back to overview Looking for JavaScript?
- Provide Static Type Checking to JavaScript
- Types as Sets - In Typescript, a type is a set of types (i.e.
string | number
) - Erased Structural Types - if an object satisfies an interface, you can use it as that interface (even if there is no declarative relationship between the two) - "duck typing"
- Types disappear at runtime - entire type system goes away in transpile step back to js
npm install -g typescript
- installs tsc, the Typescript compilertsc XXX.ts
- outputs typescript errors to the console, andXXX.js
in the directorytsc --target es2015 X
- compile with target javascript version es2015
strict
- toggle all options onnoImplicitAny
- if false (default unlessstrict
) anywhere you don't define a type and typescript can't infer it for us, typescript will useany
. If true, that will generate a compile time error insteadstrictNullChecks
- if false,null
andundefined
can be assigned to any other type. If true, must explictly mark types nullable.
- Primitives:
string
,number
,boolean
- Arrays:
type[]
orArray<type>
any
: ignore all typechecking on this typeobject
: any value with properties- Explicit type declaration:
[let/const/var] myVar: <type> = <value>;
- Implicit type declaration:
[let/const/var] myVar = <value>;
- if<type>
is inferrable from the type of<value>
then its not necessary to explicitly specify it
obj: { first: string; last?: string }
: note optionallast
- returnsundefined
if not set, rather than a runtime error
- Anonymously:
{ name: string; age: number }
- Type Alias:
type Person = { name: string; age: number; };
- Interface:
interface Person { name: string; age: number; }
Used with interfaces. Multiple inheritance allowed.
interface Point { x: number; y: number; }
interface zPoint extends Point { z: number; }
Used with types or interfaces.
type Point = { x:number; y:number; };
type zPoint = Point & { z: number };
- You can reopen an interface to add properties, but not a type
- Property name conflicts:
- With extends, you get an error
- With intersection, you get both properties
- Optional Properties: add
?
to the end of the name -number?
==number | undefined
readonly
: can't be written to during type-checking
- property type is either
string
ornumber
, and all possible values of object, when indexed by that type, will conform to the declared type
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = getStringArray();
const secondItem = myArray[1]; // secondItem is a string
- all object properties must match return type
interface NumberDictionary {
[index: string]: number;
length: number; // ok
name: string;
// Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
readonly
index signatures prevent assignment to indices
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
// Index signature in type 'ReadonlyStringArray' only permits reading.
interface Box<Type> {
contents: Type;
}
// OR
type Box<Type> = {
contents: Type;
}
let box: Box<string>;
type[]
is an alias forArray<type>
readonly type[]
is an alias forReadonlyArray<type>
- Note: no constructor, use
const roArray: ReadonlyArray<string> = ["red", "green"];
- Note: no constructor, use
- Sort of
Array
that knows exactly how many elements and their types
type StringNumberPair = [string, number];
fucntion fn(pair: StringNumberPair) {
const a = pair[0]; // string
const b = pair[1]; // number
const c = pair[2]; // index out of range
const [d, e] = pair; // d == a, e == b
}
- Length of tuple types are literals.
- You can have optional properties:
type Either2dOr3d = [number, number, number?];
- length of
Either2dOr3d
is2 | 3
- length of
- Casting
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
ORconst myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
- a specific number or string or boolean value can be referred to as a type
function printText(s: string, alignment: "left" | "right" | "center") {
!
after any expression is a type assertion that the value isn’t null or undefined:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
id: number | string
: id responds to number or string messages
- TypeScript will only allow an operation if it is valid for every member of the union
- If you have a value of a union type, you probably need to narrow before using it
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
If you narrow out all possibilities, resulting type is never
. You can use this for exhaustiveness checking:
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
//Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}
function f(param: <type>): <type> { ... }
- Anonymous functions have contextual typing:
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
// Contextual typing for function
names.forEach(function (s) {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
Or you can use a type alias:
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);
You can specify the type instead of inferring it if necessary:
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
const arr = combine([1, 2, 3], ["hello"]);
//Type 'string' is not assignable to type 'number'.
const arr = combine<string | number>([1, 2, 3], ["hello"]);
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
number?
==number | undefined
function f(x = 10) {...}
: x = 10 if not provided (or undefined is provided)- When writing a function type for a callback, never write an optional parameter unless you intend to call the function without passing that argument:
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
// Object is possibly 'undefined'.
});
- write two or more overload signatures (callable) and then a function implementation with a combined signature (not callable)
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { //can't call this signature alone
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
Rest Parameters - take unbounded number of arguments
- Implicit type annotation is
any[]
instead ofany
. - Explicit type annotation must be of the form
Array<T>
orT[]
, or a tuple type
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
Rest Arguments - provide unbounded number of arguments
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
const args = [8, 5];
const angle = Math.atan2(...args);
// A spread argument must either have a tuple type or be passed to a rest parameter.
// Inferred as 2-length tuple
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
// OR
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}