function greeter(person: string) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.textContent = greeter(user);
Interfaces :
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { firstName: "Jane", lastName: "User" };
document.body.textContent = greeter(user);
Classes :
class Student {
fullName: string;
constructor(
public firstName: string,
public middleInitial: string,
public lastName: string
) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.textContent = greeter(user);
Ex 2 :
interface User {
name : string,
id : number
}
class userAccount {
name : string;
id : number;
constructor (name : string, id : number){
this.name = name;
this.id = number;
}
}
let userObj : User = new userAccount("murphy", 1)
You can use interfaces to annotate parameters and return values to functions:
function getAdminUser():User {
...
}
function deleteUser (user: User) {
...
}
Composing Types
With TypeScript, you can create complex types by combining simple ones. There are two popular ways to do so: with Unions, and with Generics.
Unions
With a union, you can declare that a type could be one of many types. For example, you can describe a boolean type as being either true or false:
type MyBool = true | false;
A popular use-case for union types is to describe the set of strings or numbers literal that a value is allowed to be:
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
Unions provide a way to handle different types too. For example, you may have a function that takes an array or a string:
function getLength (obj: string | string[] ) {
return obj.length
}
For example, you can make a function return different values depending on whether it is passed a string or an array:
function wrapInArray (obj : string | string[]){
if(typeof obj === 'string'){
return [obj]
} else {
return obj
}
}
Generics
Generics provide variables to types. A common example is an array. An array without generics could contain anything. An array with generics can describe the values that the array contains.
type stringArray = Array<string>
type numberArray = Array<number>
type objWithNameArray = Array<{name: string}>
You can declare your own types that use generics:
interface Backpack<Type>{
add : (obj: Type)=> void;
get : ()=> Type
}
// This line is a shortcut to tell TypeScript there is a
// constant called `backpack`, and to not worry about where it came from.
declare const backpack : Backpack<string>
// object is a string, because we declared it above as the variable part of Backpack.
const object = backpack.get();
// Since the backpack variable is a string, you can't pass a number to the add function.
object.add(12)
Structural Type System
One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.
In a structural type system, if two objects have the same shape, they are considered to be of the same type.
interface Point {
x: number;
y: number;
}
function printPoint (p : Point){
console.log(`${p.x}, ${p.y}`)
}
const piont = {x:1, y: 2}
printPoint(point)
//OUTPUT : 1, 2
The point variable is never declared to be a Point type. However, TypeScript compares the shape of point to the shape of Point in the type-check. They have the same shape, so the code passes.
The shape-matching only requires a subset of the object’s fields to match.
const point3 = { x: 12, y: 26, z: 89 };
printPoint(point3); // prints "12, 26"
const rect = { x: 33, y: 3, width: 30, height: 80 };
printPoint(rect); // prints "33, 3"
const color = { hex: "#187ABF" };
printPoint(color);
//Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'.
//Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
There is no difference between how classes and objects conform to shapes:
class VirtualPoint {
x: number;
y: number;
constructor(x:number, y:number){
this.x = x;
this.y = y;
}
}
const newPoint = new VirtualPoint(22,23)
printPoint(newPoint)
//OUTPU : 22,23
If the object or class has all the required properties, TypeScript will say they match, regardless of the implementation details.
let fst: (a: any, b: any ) => any = (a,b) => a
let fst: <T,U>(a: T, b: U) => T = (a,b) = a
let o : (n: number; xs: object[]) = {n:1, xs: []}
Structural typing
type One = { p: string };
interface Two {
p: string;
}
class Three {
p = "Hello";
}
let x: One = { p: "hi" };
let two: Two = x;
two = new Three();
Discriminated Unions
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#structural-typing
The closest equivalent to data is a union of types with discriminant properties, normally called discriminated unions in TypeScript:
Unlike Haskell, the tag, or discriminant, is just a property in each object type. Each variant has an identical property with a different unit type.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(s: Shape) {
if (s.kind === "circle") {
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}
—–
Type Parameters
function liftArray<T>(t: T): Array<T> {
return [T]
}
function <T extends {length : number} >(t1: T, t2: T): T {
return t1.length > t2.length ? t1 : t2
}
//Example :
function myfn(t1: T, t2: T): T {
return t1.length > t2.length ? t1 : t2;
}
myfn(["1", "2", "3"], [1, 2]); // OUTPUT : ['1','2','3']
TypeScript can usually infer type arguments from a call based on the type of the arguments, so type arguments are usually not needed.
Because TypeScript is structural, it doesn’t need type parameters as much as nominal systems. Specifically, they are not needed to make a function polymorphic. Type parameters should only be used to propagate type information, such as constraining parameters to be the same type:
function length<T extends ArrayLike<unknown>>(t: T) : number {}
function length<t: ArrayLike<unknown>) : number {}
//Example :
function length>(t: T): number {
return t.length;
}
length([3, 4]); //2
length("Prabha"); //6
Module system
JavaScript’s modern module syntax is a bit like Haskell’s, except that any file with import or export is implicitly a module:
import { value, Type } from "npm-package";
import { other, Types } from "./local-package";
import * as prefix from "../lib/third-package";
You can also import commonjs modules — modules written using node.js’ module system:
import f = require("single-function-package");
You can export with an export list:
export { f };
function f() {
return g();
}
function g() {} // g is not exported
Or by marking each export individually:
export function f { return g() }
function g() { }
readonly and const
In JavaScript, mutability is the default, although it allows variable declarations with const to declare that the reference is immutable. The referent is still mutable:
const a = [1, 2, 3];
a.push(102); // ):
a[0] = 101; // D:
console.log("a " , a ) // a [101, 2, 3, 102]
TypeScript additionally has a readonly modifier for properties.
interface Rx {
readonly x : number;
}
let rx: Rx = { x : 1 };
rx.x = 12; //ERROR
It also ships with a mapped type Readonly<T> that makes all properties readonly:
interface X {
x : number
}
let rx: readonly<X> = { x : 1 }
rx.x = 12; // error
And it has a specific ReadonlyArray<T> type that removes side-affecting methods and prevents writing to indices of the array, as well as special syntax for this type:
let a: ReadonlyArray<number> = [1,2,3]
let b: readonly number[] = [1,2,3]
a.push(102) // ERROR
b[0] = 101 // ERROR
You can also use a const-assertion, which operates on <strong>arrays and object literals:</strong>
let a = [1,2,3] as const;
a.push(102) // ERROR
a[0] = 101 // ERROR
BASIC TYPES
Array
TypeScript, like JavaScript, allows you to work with arrays of values. Array types can be written in one of two ways. In the first, you use the type of the elements followed by [] to denote an array of that element type:
let list: number[] = [1, 2, 3];
The second way uses a generic array type, Array<elemType>:
let list: Array<number> = [1, 2, 3];
Tuple
Tuple types allow you to express an array with a fixed number of elements whose types are known, but need not be the same. For example, you may want to represent a value as a pair of a string and a number:
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
When accessing an element with a known index, the correct type is retrieved:
// OK
console.log(x[0].substring(1));
console.log(x[1].substring(1));
Property 'substring' does not exist on type 'number'.
Enum
A helpful addition to the standard set of datatypes from JavaScript is the enum. As in languages like C#, an enum is a way of giving more friendly names to sets of numeric values.
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green
//OUTPUT : 1
By default, enums begin numbering their members starting at 0. You can change this by manually setting the value of one of its members. For example, we can start the previous example at 1 instead of 0:
enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;
Or, even manually set all the values in the enum:
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;
A handy feature of enums is that you can also go from a numeric value to the name of that value in the enum. For example, if we had the value 2 but weren’t sure what that mapped to in the Color enum above, we could look up the corresponding name:
enum Color {
Red = 1,
Green,
Blue,
}
let colorName : string = Color[2]
console.log(colorName)
// OUTPUT : Green
Unknown
We may need to describe the type of variables that we do not know when we are writing an application. These values may come from dynamic content – e.g. from the user – or we may want to intentionally accept all values in our API. In these cases, we want to provide a type that tells the compiler and future readers that this variable could be anything, so we give it the unknown type.
let notSure: unknown = 4;
notSure = "maybe a string instead";
// OK, definitely a boolean
notSure = false;
If you have a variable with an unknown type, you can narrow it to something more specific by doing typeof checks, comparison checks, or more advanced type guards that will be discussed in a later chapter:
declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe;
Type 'unknown' is not assignable to type 'number'.
if (maybe === true) {
// TypeScript knows that maybe is a boolean now
const aBoolean: boolean = maybe;
// So, it cannot be a string
const aString: string = maybe;
Type 'boolean' is not assignable to type 'string'.
}
if (typeof maybe === "string") {
// TypeScript knows that maybe is a string
const aString: string = maybe;
// So, it cannot be a boolean
const aBoolean: boolean = maybe;
Type 'string' is not assignable to type 'boolean'.
}
Any
In some situations, not all type information is available or its declaration would take an inappropriate amount of effort. These may occur for values from code that has been written without TypeScript or a 3rd party library. In these cases, we might want to opt-out of type checking. To do so, we label these values with the any type:
declare function getValue(key: string) : any
// OK, return value of 'getValue' is not checked
const str : string = getValue("myString")
Never
The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be true.
// Function returning never must not have a reachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must not have a reachable end point
function infiniteLoop(): never {
while (true) {}
}
Object
object is a type that represents the non-primitive type, i.e. anything that is not number, string, boolean, bigint, symbol, null, or undefined.
For example:
declare function create(o: object | null): void;
// OK
create({ prop: 0 });
create(null);
create(42);
Argument of type '42' is not assignable to parameter of type 'object | null'.
create("string");
Argument of type '"string"' is not assignable to parameter of type 'object | null'.
create(false);
Argument of type 'false' is not assignable to parameter of type 'object | null'.
create(undefined);
Argument of type 'undefined' is not assignable to parameter of type 'object | null'.
Type assertions
Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does. Usually, this will happen when you know the type of some entity could be more specific than its current type.
Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but it performs no special checking or restructuring of data. It has no runtime impact and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.
Type assertions have two forms.
One is the as-syntax:
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;Try
The other version is the “angle-bracket” syntax:
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;