Typescript
Page content
Typescript
offers allJS
’s features, and typing system.- It also supports object-oriented programming as
JS
.
1. Installation
1.1 Node.js
and npm
(node package manager)
-
They are required to setup the environment. After installing them, you can check using the following commands:
node -v # checks the version npm -v # checks the version
1.2 typeScript
npm install -g typescript
- The line above installs the
TypeScript
Compilertsc
globally. - Also, you can use
npx
to runtsc
from a localnode_modules
package instead.
2. Hello world
// typeScript filename: greeting.ts
var greeting: string = "Hello World";
console.log(greeting);
# bash: compilation (ts -> js)
tsc greeting.ts
# bash: run javascript file
node greeting.js
3. Compilation
- Because browsers and
Node.js
process onlyJavaScript
, you must compile yourTypeScript
code before running or debugging it.tsc <file1>.ts <file2>.ts --outDir ./dist
keeps the directory clean.
4. Data types
4.1 Primitive types
4.1.1 string
var greeting: string = "Hello World";
console.log(greeting.toLowerCase()); // "hello world"
console.log(greeting); // "Hello World"
// Multi-line strings
let firstName: string = `Bob`;
let profile: string = `Hello
I'm ${firstName}.`;
4.1.2 number
- There’s no equivalent to
int
orfloat
, but big integers get the typebigint
.
// Binary numbers := 0b... or 0B...
let bin = 0b100;
let anotherBin: number = 0B010;
// Octal numbers := 0o...
let octal: number = 0o10;
// Hexadecimal numbers := 0x... or 0X...
// The digits after the 0x must be in the range (0123456789ABCDEF).
let hexadecimal: number = 0XA;
// Big integers := ...n
let big: bigint = 9007199254740991n;
4.1.3 boolean
true
andfalse
4.2 Array
// let arrayName: type[];
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
- Mixed types
let scores : (string | number)[];
scores = ['Bob', 100]; // [ 'Bob', 100 ]
Array.forEach()
,Array.map()
,Array.reduce()
, andArray.filter()
are useful array methods.
4.2.1 Array.forEach()
- It calls a function for each element in the array.
let num = [1, 2, 3];
num.forEach(function (value) {
console.log(value); // 1 2 3
});
4.2.2 Array.map()
let arr: number[] = [1, 2, 3];
let square: number[] = arr.map(e => e * e);
console.log(square); // [ 1, 4, 9 ]
4.2.3 Array.reduce()
- It applies a function against two array values to reduce it to a single value.
let arr: number[] = [1, 2, 3];
let val = arr.reduce(function(a, b) { // (a,b):= (previous, current)
return a + b;
})
console.log(val); // 6
4.2.4 Array.filter()
- It creates a new array with all elements that pass the test implemented by the provided function.
function isEven(n: number) {
if(n % 2 == 0) {
return true;
} else {
return false;
}
}
console.log([1, 2, 3].filter(isEven)); // [ 2 ]
4.3 any
- You can use whenever you don’t want a particular value to cause typechecking errors, except instead of
never
.
let looselyTyped: any = 4;
looselyTyped.ifItExists(); // OK, ifItExists might exist at runtime
looselyTyped.toFixed(); // OK, toFixed exists (but the compiler doesn't check)
let strictlyTyped: unknown = 4;
strictlyTyped.toFixed();
4.4 never
- It indicates that a function will never return or a value will never be assignable to a particular type. Typically, it represents the return type of a function that always throws an error.
function raiseError(message: string): never {
throw new Error(message);
}
4.5 Type annotations on variables
4.5.1 var
var
is valid.var name: string = 'Simon';
4.5.2 let
let
creates a variable.- It allows you to declare block-level variables.
- The declared variable is available from the block is enclosed in.
let name: string = 'Simon';
4.5.3 const
const
creates a constant value. (mostly)const
variables are never intended to change.
const Pi: number = 3.14;
4.6 Composing types
4.6.1 Union (|
)
- It declares that an object can be more than one type.
type StringOrNumber = string | number;
4.7 Date
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toString()}`);
}
greet("Bob", new Date(2023, 5, 28));
4.8 enum
- You can define a set of named constants using
enum
.
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;
4.9 object
- It represents the non-primitive type.
- It refers to any JavaScript value with properties.
- To define an object type, we list its properties and their types.
- Object types are functions, arrays, classes, etc.
function printCoord(pt: { x: number; y: number }) { }
// combined representation
let employee: {
firstName: string;
lastName: string;
} = {
firstName: 'Jane',
lastName: 'Doe',
};
// empty
let vacant: {} = {};
4.9.1 Optional properties
- When you read from an optional property, you must check for
undefined
before using it.
function printName(obj: { first: string; last?: string }) { }
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
4.9.2 Readonly properties
- A readonly property should be modifiable only when the object first created.
interface Cat{
name: string;
breed: string;
}
function makeCat(name: string, breed: string): Readonly<Cat> {
return { name, breed };
}
const mia = makeCat("Mia", "Calico");
// mia.breed = "Tabby"; // error
// OR add `readonly` in front of the name to make it read only
// interface Cat{
// readonly name: string;
// breed: string;
// }
-
readonly tuple:=
readonly [number, number, number]
-
readonly array:= `const constArr = [1, 2] as const;
4.10 null
and undefined
- These primitive values are used to signal absent or uninitialized value.
let u: undefined = undefined;
let n: null = null;
4.11 Tuples
const passingResponse: [string, number] = ["{}", 200]; // [ '{}', 200 ]
let colour: [number, number, number] = [255, 0, 0]; // [ 255, 0, 0 ]
type PayStubs = [StaffAccount, ...number[]];
const payStubs: PayStubs[] = [
[staff[0], 250],
[staff[1], 250, 260],
[staff[0], 300, 300, 300],
];
type ThreeDCoordinate = [ x: number, y: number, z: number ];
function add3DCoordinate(c1: ThreeDCoordinate, c2: ThreeDCoordinate): ThreeDCoordinate {
return [
c1[0] + c2[0],
c1[1] + c2[1],
c1[2] + c2[2],
]
}
console.log(add3DCoordinate([0, 10, 0], [10, 20, 30])); // [ 10, 30, 30 ]
4.12 unknown
- It describes the type of variables that we do not know when we are writing the code.
let notSure: unknown = 4; // OK
notSure = "maybe a string instead"; // OK
notSure = false; // OK
4.13 void
- It is a little like the opposite of any: the absence of having any type at all. It is a good practice to add the void type as the return type of a function or a method that doesn’t return any value.
function warnUser(): void {
console.log("This is my warning message");
}
4.14 bigint
- For very large integers.
const oneHundred: bigint = BigInt(100);
const anotherHundred: bigint = 100n;
4.15 symbol
symbol
:= immutable and unique
const value1 = Symbol('hello');
const value2 = Symbol('hello');
console.log(value1 === value2); // false
// symbol
for()
keyFor()
toSource()
toString()
valueOf()
5. Functions
- Declaring a function:
let sayHi: (name: string) => string;
sayHi = function (name) {
return `Hi ${name}!`;
};
console.log(sayHi('Bob')); // Hi Bob!
5.1 Parameter type annotations
function greet(name: string) {
console.log("Hello " + name.toUpperCase());
}
5.2 Return type annotations
function getNumber(): number {
return 1;
}
5.3 Anonymous functions
const names = ["Alice", "Bob", "Eve"];
names.forEach(function (s) {
console.log(s.toUpperCase());
});
names.forEach((s) => {
console.log(s.toUpperCase());
});
6. Interfaces and type aliases
- Key distinction is that a
type
cannot be reopened to add new properties, whereas aninterface
is always extendable. - Interfaces define contracts and provide explicit names for type checking.
- Interfaces may have optional properties or readonly properties.
- Interfaces can be used as function types.
6.1 Interfaces
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) { }
interface ThisPoint extends Point { }
- An interface can extend one or multiple interfaces or a class.
interface <InterfaceX> {
x(): void
}
interface <InterfaceY> {
y(): void
}
interface <InterfaceZ> extends <InterfaceX>, <InterfaceY> {
z(): string
}
- Only the class itself or its subclasses are allowed to implement the interface, if a class contains private or protected members.
class Cat {
private _age: number = 0;
}
interface StrayCat extends Cat {
play(): void
}
class YellowStrayCat extends Cat implements StrayCat {
play() {
console.log('I like toys.')
}
}
const yellowStrayCat = new YellowStrayCat();
yellowStrayCat.play();
6.2 Type aliases
- A type alias is literally that - a name for any type.
type Point = {
x: number;
y: number;
};
function printCoord(pt: Point) { }
// intersection
type <bothType> = <firstType> & <secondType>; // <bothType> will have all properties from both the first and second.
// union
type alphanumeric = string | number; // Alphanumerics holds a value of either string or number.
let input: alphanumeric;
input = 'Bob'; // valid
input = 1900; // valid
6.2.1 String literal type
type MouseEvent: 'click' | 'dblclick' | 'mouseup' | 'mousedown';
let mouseEvent: MouseEvent;
mouseEvent = 'click'; // valid
mouseEvent = 'dblclick'; // valid
6.2.2 Numeric literal type
function rollDice(times: 1 | 2): void {
for (let i=0;i<times;i++) {
let dice: number = Math.floor((Math.random() * 5) + 1);
console.log(dice);
}
}
rollDice(2);
6.2.3 Type casting
let a: typeA;
let b = <typeB>a;
7. Type assertions
- They are removed by the compiler, like a type annotation, and won’t affect the runtime behavior of your code.
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); // you can use <>, except if the code is in a .tsx file
8. Literal types
// combining literal types (return)
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
9. Narrowing
- Refining types to more specific types than declared is called narrowing.
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input; // padding:= number
}
return padding + input; // padding:= string
}
10. Type predicate
- To define a user-defined type guard, we simply need to define a function of which return type is a type predicate:
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
11. Parameters
11.1 Rest parameters
- It allows you to represent an indefinite number (zero or more) of arguments as an array.
function <function_name>(...<parameter_name>: <parameter_type>[]) { }
- Rules:
- A function has only one rest parameter.
- The rest parameter appears last in the parameter list.
- The type of the rest parameter is array.
11.2 Default parameters
- The default initialised value for the omitted parameters.
- Default parameters don’t need to appear after the required parameters. When a default parameter appears before a required parameter, you need to explicitly pass
undefined
to get the default initialised value.
function <function_name>(<parameter_name>: <parameter_type>=<default_value>,...): <return_type_if_any>{ }
11.3 Optional parameters
- Optional parameters must come after the required parameters.
- Using
?
after the parameter name makes a function parameter optional.
function <function_name>(<parameter_name>?: <parameter_type>=<default_value>,...): <return_type_if_any>{ }
12. Class
class Cat {
catName: string;
constructor(catName: string) {
this.catName = catName;
}
getCatName(): string {
return `${this.catName}`;
}
}
let cat = new Cat('Puffy');
12.1 Getters and setters
class Cat {
private _age: number = 0;
public get age(): number {
return this._age;
}
public set age(newAge: number) {
if (newAge < 0) {
throw new Error('Age is invalid.');
}
else if (newAge > 1) {
console.log('Not a kitten');
}
this._age = newAge;
}
}
const cat = new Cat();
console.log(cat); // 0
cat.age = 5; // Not a kitten
console.log(cat); // 5
cat.age = -1; // Age is invalid.
12.2 Member visibility
private
,protected
andpublic
.
12.3 Abstract class
- It defines common behaviours for derived classes to extend.
- Unlike a regular class, it cannot be instantiated directly.
- To use it, you must inherit and implement its abstract methods.
- It often contains one or more abstract methods.
abstract class PokemonCharacter {
constructor() {}
walk() {}
attack() {
console.log(`${this.name} attack with ${this.getSpecialAttack()}`);
}
abstract getSpecialAttack(): string;
abstract get name(): string;
}
class Pikachu extends PokemonCharacter {
get name(): string {
return "Pikachu";
}
getSpecialAttack(): string {
return "Thunder Shock";
}
}
const pikachu = new Pikachu();
pikachu.attack();
13. Static methods and properties
- A static property is shared among all instances of a class.
class Cat {
static counter: number = 0;
constructor(private catName: string) {
Cat.counter++;
}
public static getCounter() {
return Cat.counter;
}
}
let simba = new Cat('Simba');
let felix = new Cat('Felix');
console.log(Cat.counter); // 2
console.log(Cat.getCounter()); // 2
14. Operators
14.1 Non-null assertion operator !
!
says that it will never benull
orundefined
.
interface Person {
name: string
age: number
}
function printName(person?: Person) {
// If we don't put an exclamation mark (person!) an error will
// occur, 'person' is possibly 'undefined'.
console.log(`The name is ${person!.name}`)
}
14.2 Nullish coalescing operator ??
??
returns the first argument if notnull
/undefined
. Otherwise, it returns the second one.
a ?? b // equals to
( a!== null && a!== undefined ) ? a : b
14.3 in
in
operator determines if an object has a property with a name.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
14.4 instanceof
- It has an operator for checking whether or not a value is an “instance” of another value.
15. Callback functions
- A
callback
is used to pass a function to another function. So that within the called function, it can call back the function you passed to it.
interface Greeter {
(message: string): void;
}
function sayHi(callback: Greeter) {
callback('Hi!');
}
const myGreeter: Greeter = (message: string) => {
console.log(`Greeting: ${message}`);
};
sayHi(myGreeter); // "Greeting: Hi!"
16. Optionals
function printIngredient(quantity: string, ingredient: string, extra?: string) {
console.log(`${quantity} ${ingredient} ${extra ? ` ${extra}` : ""}`);
}
printIngredient("1C", "Flour");
printIngredient("1C", "Sugar", "Something more")
interface User {
id: string;
info?: {
email?: string;
}
}
function getEmailEasy(user: User): string {
return user?.info?.email ?? "";
}
function addWithCallback(x: number, y: number, callback?: () => void) {
console.log([x,y]);
callback?.();
}
17. Types
17.1 Generics
- Generics can create a component that can work over a variety of types rather than a single one.
function identity<Type>(arg: Type): Type {
return arg;
}
function identityArr<Type>(arg: Type[]): Type[] {
console.log(arg.length);
return arg;
}
function simpleState<T>(initial: T): [() => T, (v: T) => void] {
let val: T = initial;
return [
() => val,
(v: T) => {
val = v;
},
];
}
const [str1getter, str1setter] = simpleState("hello");
console.log(str1getter()); // hello
str1setter("bye");
console.log(str1getter()); // bye
const [int1getter, int1setter] = simpleState(1);
console.log(int1getter()); // 1
int1setter(2);
console.log(int1getter()); // 2
17.2 keyof
Type Operator
- The
keyof
operator takes an object type and produces a string or numeric literal union of its keys.
function extract<DataType, KeyType extends keyof DataType>(
items: DataType[],
key: KeyType
): DataType[KeyType][] {
return items.map((item) => item[key]);
}
const people = [
{ name: 'Bob', age: 20},
{ name: 'Alice', age: 30}
]
console.log(extract(people, "age"));
console.log(extract(people, "name"));
17.3 typeof
Type Operator
typeof
shows the type of values we have at runtime.
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>; // type P = { x: number; y: number; }
17.4 Indexed Access Types
- An indexed access type can look up a specific property on another type.
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // type Age = number
17.5 Conditional Types
- They help describe the relation between the types of inputs and outputs.
// SomeType extends OtherType ? TrueType : FalseType;
17.6 Mapped Types
- As a generic type, it uses a union of
PropertyKey
s (frequently created via a keyof) to iterate through keys to create a type:
// definition
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type Features = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<Features> // type FeatureOptions = { darkMode: boolean; newUserProfile: boolean; }
- Key remapping via
as
:
// definition
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}
//
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>; // type LazyPerson = { getName: () => string; getAge: () => number; getLocation: () => string; }
17.7 Template Literal Types
- They are build on string literal types and can expand into many strings via unions.
type Size = 'small' | 'medium';
type Colour = 'red' | 'blue';
type Style = `${Size}-${Colour}`;
function applyStyle(style: Style) {
}
applyStyle('small-primary');
18. Utility Types
- They facilitate common type transformations.
18.1 Awaited<Type>
- It extracts the type that is being returned by a promise. It helps us to avoid using
.then()
andawait
the same promise repeatedly.
async function getCat(): Promise<{
name: string;
age: number
}> {
return { name: "Simba", age: 1 };
}
type CatType = Awaited<ReturnType<typeof getCat>>; // [ name: string, age: number ]
18.2 Capitalize<StringType>
- It converts the first character into the string to an uppercase equivalent.
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>; // "Hello, world"
18.3 ConstructParameters<Type>
- It constructs a tuple or an array type from the types of a constructor function type.
class MyClass {
constructor(x: number, str: string) {}
}
type T0 = ConstructorParameters<typeof MyClass>; // [ x: number, str: string ]
18.4 Exclude<UnionType, ExcludedMembers
- It constructs a type by excluding all union members that are assignable to
ExcludedMembers
.
type ExcludedType = Exclude<"a" | "str", "str">; // "a"
18.5 Extract<Type, Union>
- It constructs a new type by exracting all union members that are assignable to
Union
. It is the opposite ofExclude
.
type ExtractedType = Extract<"a" | "str", "str">; // "str"
18.6 InstanceType<Type>
- It constructs a type consisting of the instance type of a constructor function in Type. It is similar to
ReturnType
, but it acts on a class constructor.
class Cat {
name: string;
age: number;
constructor(cat: { name: string; age: number }) {
this.name = cat.name;
this.age = cat.age;
}
}
type CatType = InstanceType<typeof Cat>; // [ name: string; age: number ]
18.7 Lowercase<StringType>
- It converts each character into the string to the lowercase equivalent.
type Greeting = "Hello, world";
type QuietGreeting = Lowercase<Greeting>; // "hello, world"
18.8 NonNullable<Type>
- It constructs a new type by excluding
null
andundefined
.
type Type = string | number | null | undefined;
type NonNullableType = NonNullable<Type>; // "string" | "number"
18.9 Omit<Type, Keys>
- It constructs a type by picking all properties from Type, then removing Keys. It is the opposite of
Pick
.
type Type = { a: number, str: string };
type OmitType = Omit<Type, "a">; // { str: string }
18.10 Parameters<Type>
- It constucts a tuple types from the types used in the parameters of a function type
Type
.
const sayHi = (str: string) => {
console.log("Hi");
};
type FunctionParameters = Parameters<typeof sayHi>; // [str: string]
18.11 Partial<Type>
- It constructs a type with all properties of Type set to optional.
type Type = { a: number, str: string };
type PartialType = Partial<Type>; // { a?: number; str?: string }
18.12 Pick<Type, Keys>
- It constructs a type by picking the set of properties Keys from Type.
type Type = { a: number, str: string };
type PickType = Pick<Type, "a">; // { a: number }
18.13 Readonly<Type>
- It creates a new type with all properties of Type set to
readonly
.
type Type = { a: number, str: string };
type ReadonlyType = Readonly<Type>; // { readonly a: number; readonly str: string }
18.14 Record<Keys, Type>
- It constructs an object type of which property keys are Keys and of which property values are Type.
interface CatInfo {
age: number;
breed: string;
}
type CatName = "puffy" | "mia";
const cats: Record<CatName, CatInfo> = {
puffy: { age: 1, breed: "Persian" },
mia: { age: 2, breed: "Stray" }
};
console.log(cats.puffy); // { "age": 1, "breed": "Persian" }
18.15 Required<Type>
- It constructs a type consisting of all properties of Type set to required. It is the opposite of
Partial
.
type Type = { a?: number, str?: string };
type RequiredType = Required<Type>; // { a: number; str: string }
18.16 ReturnType<Type>
- It constructs a type of the return type of the function. (Use in
useState
hook in React)
const getCat = () => ({
name: "Simba",
age: 1
});
type FunctionReturnType = ReturnType<typeof getCat>; // { name: string; age: number; }
18.17 Uncapitalize<StringType>
- It converts the first character into the string to a lowercase equivalent.
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // "hELLO WORLD"
18.18 Uppercase<StringType>
- It converts each character into the string to the uppercase version.
type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>; // "HELLO, WORLD"