Logo
TypeScriptJavaScriptFrontend DevelopmentWeb Development

TypeScript Basic Notes

Thu May 05 2022Sat May 28 202215 minutes

TypeScript Basic Notes

Modules

Classic Module Resolution

import { a } from './module':
  • /root/src/folder/module.ts.
  • /root/src/folder/module.d.ts.
import { a } from 'module':
  • /root/src/folder/module.ts.
  • /root/src/folder/module.d.ts.
  • /root/src/module.ts.
  • /root/src/module.d.ts.
  • /root/module.ts.
  • /root/module.d.ts.
  • /module.ts.
  • /module.d.ts.

Node Module Resolution

const x = require('./module'):
  • /root/src/module.ts.
  • /root/src/module.tsx.
  • /root/src/module.d.ts.
  • /root/src/module/package.json + { "types": "lib/mainModule.ts" } = /root/src/module/lib/mainModule.ts.
  • /root/src/module/index.ts.
  • /root/src/module/index.tsx.
  • /root/src/module/index.d.ts.
const x = require('module'):
  • /root/src/node_modules/module.ts.
  • /root/src/node_modules/module.tsx.
  • /root/src/node_modules/module.d.ts.
  • /root/src/node_modules/module/package.json (if it specifies a types property).
  • /root/src/node_modules/@types/module.d.ts.
  • /root/src/node_modules/module/index.ts.
  • /root/src/node_modules/module/index.tsx.
  • /root/src/node_modules/module/index.d.ts.
  • /root/node_modules/module.ts.
  • /root/node_modules/module.tsx.
  • /root/node_modules/module.d.ts.
  • /root/node_modules/module/package.json (if it specifies a types property).
  • /root/node_modules/@types/module.d.ts.
  • /root/node_modules/module/index.ts.
  • /root/node_modules/module/index.tsx.
  • /root/node_modules/module/index.d.ts.
  • /node_modules/module.ts.
  • /node_modules/module.tsx.
  • /node_modules/module.d.ts.
  • /node_modules/module/package.json (if it specifies a types property).
  • /node_modules/@types/module.d.ts.
  • /node_modules/module/index.ts.
  • /node_modules/module/index.tsx.
  • /node_modules/module/index.d.ts.

Enum Types

const enums don’t have representation at runtime, its member values are used directly.
1// Source code:
2const enum NoYes {
3 No,
4 Yes,
5}
6
7function toGerman(value: NoYes) {
8 switch (value) {
9 case NoYes.No:
10 return 'Neither';
11 case NoYes.Yes:
12 return 'Ja';
13 }
14}
15
16// Compiles to:
17function toGerman(value) {
18 switch (value) {
19 case 'No' /* No */:
20 return 'Neither';
21 case 'Yes' /* Yes */:
22 return 'Ja';
23 }
24}
Non-const enums are objects:
1// Source code:
2enum Tristate {
3 False,
4 True,
5 Unknown,
6}
7
8// Compiles to:
9let Tristate;
10(function (Tristate) {
11 Tristate[(Tristate.False = 0)] = 'False';
12 Tristate[(Tristate.True = 1)] = 'True';
13 Tristate[(Tristate.Unknown = 2)] = 'Unknown';
14})(Tristate || (Tristate = {}));
15
16console.log(Tristate[0]); // 'False'
17console.log(Tristate.False); // 0
18console.log(Tristate[Tristate.False]); // 'False' because `Tristate.False == 0`
1enum NoYes {
2 No = 'NO!',
3 Yes = 'YES!',
4}
5
6let NoYes;
7(function (NoYes) {
8 NoYes.No = 'NO!';
9 NoYes.Yes = 'YES!';
10})(NoYes || (NoYes = {}));

Interface

  • Type aliases may not participate in declaration merging, but interfaces can.
  • Interfaces may only be used to declare the shapes of object, not re-name primitives.
  • The key distinction is that a type cannot be re-opened to add new properties, an interface which is always extendable.
1interface Window {
2 title: string;
3}
4
5interface Window {
6 ts: TypeScriptAPI;
7}
8
9const src = 'const a = "Hello World"';
10window.ts.transpileModule(src, {});

Index Signature

1const MyArray = [
2 { name: 'Alice', age: 15 },
3 { name: 'Bob', age: 23 },
4 { name: 'Eve', age: 38 },
5];
6
7type Person = typeof MyArray[number];
8// type Person = {
9// name: string;
10// age: number;
11// }
12
13type Age = typeof MyArray[number]['age'];
14// type Age = number
15
16type Age2 = Person['age'];
17// type Age2 = number
{ [K in keyof T]: indexedType }[keyof T] 返回键名 (键名组成的联合类型):
1type PickByValueType<T, ValueType> = Pick<
2 T,
3 { [K in keyof T]-?: T[K] extends ValueType ? K : never }[keyof T]
4>;
5
6type OmitByValueType<T, ValueType> = Pick<
7 T,
8 { [K in keyof T]-?: T[K] extends ValueType ? never : K }[keyof T]
9>;
10
11type RequiredKeys<T> = {
12 [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
13}[keyof T];
14
15type OptionalKeys<T> = {
16 [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
17}[keyof T];
18
19type FunctionTypeKeys<T extends object> = {
20 [K in keyof T]-?: T[K] extends Function ? K : never;
21}[keyof T];
22
23type Filter<T extends object, ValueType> = {
24 [K in keyof T as ValueType extends T[K] ? K : never]: T[K];
25}; // Filter<{name: string; id: number;}, string> => {name: string;}

Template Literal Types

  • Based on literal types.
  • 4 intrinsic String Manipulation Types:
    • Uppercase<StringType>.
    • Lowercase<StringType>.
    • Capitalize<StringType>.
    • Uncapitalize<StringType>.
1interface PropEventSource<Type> {
2 on<Key extends string & keyof Type>(
3 eventName: `${Key}Changed`,
4 callback: (newValue: Type[Key]) => void
5 ): void;
6}
7
8// Create a "watched object" with an 'on' method
9// so that you can watch for changes to properties.
10declare function makeWatchedObject<Type>(
11 obj: Type
12): Type & PropEventSource<Type>;
13
14const person = makeWatchedObject({
15 firstName: 'Yi',
16 lastName: 'Long',
17 age: 26,
18});
19
20person.on('firstNameChanged', newName => {
21 // (parameter) newName: string
22 console.log(`new name is ${newName.toUpperCase()}`);
23});
24
25person.on('ageChanged', newAge => {
26 // (parameter) newAge: number
27 if (newAge < 0) {
28 console.warn('warning! negative age');
29 }
30});
31
32// It's typo-resistent
33person.on('firstName', () => {});
34// Argument of type '"firstName"' is not assignable to
35// parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
36
37person.on('fstNameChanged', () => {});
38// Argument of type '"fstNameChanged"' is not assignable to
39// parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

Generic Types

1interface Lengthwise {
2 length: number;
3}
4
5function createList<T extends number | Lengthwise>(): T[] {
6 return [] as T[];
7}
8
9const numberList = createList<number>(); // ok
10const stringList = createList<string>(); // ok
11const arrayList = createList<any[]>(); // ok
12const boolList = createList<boolean>(); // error
在类型编程里, 泛型就是变量:
1function pick<T extends object, U extends keyof T>(obj: T, keys: U[]): T[U][] {
2 return keys.map(key => obj[key]);
3}

Union Types

1interface Square {
2 kind: 'square';
3 size: number;
4}
5
6interface Rectangle {
7 kind: 'rectangle';
8 width: number;
9 height: number;
10}
11
12interface Circle {
13 kind: 'circle';
14 radius: number;
15}
16
17type Shape = Square | Rectangle | Circle;
18
19function area(s: Shape) {
20 switch (s.kind) {
21 case 'square':
22 return s.size * s.size;
23 case 'rectangle':
24 return s.width * s.height;
25 case 'circle':
26 return Math.PI * s.radius ** 2;
27 default: {
28 const _exhaustiveCheck: never = s;
29 return _exhaustiveCheck;
30 }
31 }
32}

Intersection Types

intersection type 具有所有类型的功能:
1function extend<T, U>(first: T, second: U): T & U {
2 const result = {} as T & U;
3 for (const id in first) {
4 (result as T)[id] = first[id];
5 }
6 for (const id in second) {
7 if (!Object.prototype.hasOwnProperty.call(result, id)) {
8 (result as U)[id] = second[id];
9 }
10 }
11
12 return result;
13}
14
15const x = extend({ a: 'hello' }, { b: 42 });
16
17// 现在 x 拥有了 a 属性与 b 属性
18const a = x.a;
19const b = x.b;

Conditional Types

  • Basic conditional types just like if else statement.
  • Nested conditional types just like switch case statement.
  • Distributive conditional types just like map statement (loop statement) on union type.
  • Conditional types make TypeScript become real programing type system: TypeScript type system is Turing Complete.
  • Conditional types in which checked type is naked type parameter are called DCT.
  • DCT are automatically distributed over union types during instantiation.
  • When conditional types act on a generic type, they become distributive when given a union type.
  • ( A | B | C ) extends T ? X : Y 相当于 (A extends T ? X : Y) | (B extends T ? X : Y) | (B extends T ? X : Y).
  • 没有被额外包装的联合类型参数, 在条件类型进行判定时会将联合类型分发, 分别进行判断.
1// "string" | "function"
2type T1 = TypeName<string | (() => void)>;
3
4// "string" | "object"
5type T2 = TypeName<string | string[]>;
6
7// "object"
8type T3 = TypeName<string[] | number[]>;
1type Naked<T> = T extends boolean ? 'Y' : 'N';
2type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N';
3
4/*
5 * 先分发到 Naked<number> | Naked<boolean>
6 * 结果是 "N" | "Y"
7 */
8type Distributed = Naked<number | boolean>;
9
10/*
11 * 不会分发 直接是 [number | boolean] extends [boolean]
12 * 结果是 "N"
13 */
14type NotDistributed = Wrapped<number | boolean>;

Mapped Types

Builtin Mapped Types

Basic Mapped Types

1type Readonly<T> = { readonly [P in keyof T]: T[P] };
2type Partial<T> = { [P in keyof T]?: T[P] };
3type ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] };
4type Required<T> = { [P in keyof T]-?: T[P] };
5type Nullable<T> = { [P in keyof T]: T[P] | null };
6type NonNullable<T> = T extends null | undefined ? never : T;
7type Clone<T> = { [P in keyof T]: T[P] };
8type Stringify<T> = { [P in keyof T]: string };

Union Mapped Types

With distributive conditional type:
1type Extract<T, U> = T extends U ? T : never;
2type Exclude<T, U> = T extends U ? never : T;

Key Mapped Types

1type Pick<T, K extends keyof T> = { [P in K]: T[P] };
2type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
3type Record<K extends keyof any, T> = { [P in K]: T };

Function Mapped Types

1type Parameters<T extends (...args: any) => any> = T extends (
2 ...args: infer P
3) => any
4 ? P
5 : never;
6
7type ConstructorParameters<T extends new (...args: any) => any> =
8 T extends new (...args: infer P) => any ? P : never;
9
10type ReturnType<T extends (...args: any) => any> = T extends (
11 ...args: any[]
12) => infer R
13 ? R
14 : any;
15
16type InstanceType<T extends new (...args: any) => any> = T extends new (
17 ...args: any
18) => infer R
19 ? R
20 : any;
21
22type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any
23 ? U
24 : unknown;

Custom Mapped Types

Combine with:
  • in keyof.
  • readonly.
  • ?.
  • -.
  • as.
  • Template literal types.
  • Conditional types.
  • Builtin types.
  • Other mapped types.
  • Other custom types.
1// Removes 'readonly' attributes from a type's properties
2type Mutable<Type> = {
3 -readonly [Property in keyof Type]: Type[Property];
4};
5
6interface LockedAccount {
7 readonly id: string;
8 readonly name: string;
9}
10
11type UnlockedAccount = Mutable<LockedAccount>;
12// type UnlockedAccount = {
13// id: string;
14// name: string;
15// };
1// Mapped types via `as` type
2type Getters<Type> = {
3 [Property in keyof Type as `get${Capitalize<
4 string & Property
5 >}`]: () => Type[Property];
6};
7
8interface Person {
9 name: string;
10 age: number;
11 location: string;
12}
13
14type LazyPerson = Getters<Person>;
15// type LazyPerson = {
16// getName: () => string;
17// getAge: () => number;
18// getLocation: () => string;
19// }
1// Remove the 'kind' property
2type RemoveKindField<Type> = {
3 [Property in keyof Type as Exclude<Property, 'kind'>]: Type[Property];
4};
5
6interface Circle {
7 kind: 'circle';
8 radius: number;
9}
10
11type KindlessCircle = RemoveKindField<Circle>;
12// type KindlessCircle = {
13// radius: number;
14// }
1// Mapped type via conditional type
2type ExtractPII<Type> = {
3 [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
4};
5
6interface DBFields {
7 id: { format: 'incrementing' };
8 name: { type: string; pii: true };
9}
10
11type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
12// type ObjectsNeedingGDPRDeletion = {
13// id: false;
14// name: true;
15// }

Utility Types

Null Types

1type Nullish = null | undefined;
2type Nullable<T> = T | null;
3type NonUndefinedable<A> = A extends undefined ? never : A;
4type NonNullable<T> = T extends null | undefined ? never : T;

Boolean Types

1type Falsy = false | '' | 0 | null | undefined;
2const isFalsy = (val: unknown): val is Falsy => !val;

Primitive Types

1type Primitive = string | number | boolean | bigint | symbol | null | undefined;
2
3const isPrimitive = (val: unknown): val is Primitive => {
4 if (val === null || val === undefined) {
5 return true;
6 }
7
8 const typeDef = typeof val;
9
10 const primitiveNonNullishTypes = [
11 'string',
12 'number',
13 'bigint',
14 'boolean',
15 'symbol',
16 ];
17
18 return primitiveNonNullishTypes.includes(typeDef);
19};

Promise Types

1// TypeScript 4.5.
2// Get naked Promise<T> type.
3type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
4
5// A = string.
6type A = Awaited<Promise<string>>;
7
8// B = number.
9type B = Awaited<Promise<Promise<number>>>;
10
11// C = boolean | number.
12type C = Awaited<boolean | Promise<number>>;

Proxy Types

1interface Proxy<T> {
2 get(): T;
3 set(value: T): void;
4}
5
6type Proxify<T> = { [P in keyof T]: Proxy<T[P]> };

Recursive Types

1type DeepReadonly<T> = {
2 +readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
3};
4
5type DeepMutable<T> = {
6 -readonly [P in keyof T]: T[P] extends object ? DeepMutable<T[P]> : T[P];
7};
8
9type DeepPartial<T> = {
10 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
11};
12
13type DeepRequired<T> = {
14 [P in keyof T]-?: T[P] extends object | undefined ? DeepRequired<T[P]> : T[P];
15};

Lodash Types

1type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

Type Inference

类型系统在获得足够的信息后, 能将 infer 后跟随的类型参数推导出来, 最后返回这个推导结果:
1type Parameters<T extends (...args: any) => any> = T extends (
2 ...args: infer P
3) => any
4 ? P
5 : never;
6
7type ConstructorParameters<T extends new (...args: any) => any> =
8 T extends new (...args: infer P) => any ? P : never;
9
10type ReturnType<T extends (...args: any) => any> = T extends (
11 ...args: any[]
12) => infer R
13 ? R
14 : any;
15
16type InstanceType<T extends new (...args: any) => any> = T extends new (
17 ...args: any
18) => infer R
19 ? R
20 : any;
1const foo = (): string => {
2 return 'sabertaz';
3};
4
5// string
6type FooReturnType = ReturnType<typeof foo>;

Type Guard

Discriminated Union Type Guard

1interface Teacher {
2 kind: 'Teacher';
3 teacherId: string;
4}
5
6interface Student {
7 kind: 'Student';
8 studentId: string;
9}
10
11type Attendee = Teacher | Student;
12
13function getId(attendee: Attendee) {
14 switch (attendee.kind) {
15 case 'Teacher':
16 // %inferred-type: { kind: "Teacher"; teacherId: string; }
17 return attendee.teacherId;
18 case 'Student':
19 // %inferred-type: { kind: "Student"; studentId: string; }
20 return attendee.studentId;
21 default:
22 throw new Error('Unsupported type');
23 }
24}

Never Type Guard

  • The never type is assignable to every type.
  • No type is assignable to never (except never itself).
1interface Triangle {
2 kind: 'triangle';
3 sideLength: number;
4}
5
6type Shape = Circle | Square | Triangle;
7
8function getArea(shape: Shape) {
9 switch (shape.kind) {
10 case 'circle':
11 return Math.PI * shape.radius ** 2;
12 case 'square':
13 return shape.sideLength ** 2;
14 default: {
15 // Type 'Triangle' is not assignable to type 'never'.
16 const _exhaustiveCheck: never = shape;
17 return _exhaustiveCheck;
18 }
19 }
20}
Exhaustiveness checks:
1class UnsupportedValueError extends Error {
2 constructor(value: never) {
3 super(`Unsupported value: ${value}`);
4 }
5}
6
7function toGerman4(value: NoYesStrings): string {
8 switch (value) {
9 case 'Yes':
10 return 'Ja';
11 default:
12 // @ts-expect-error: Argument of type '"No"'
13 // is not assignable to parameter of type 'never'. (2345)
14 throw new UnsupportedValueError(value);
15 }
16}

Type Predicate

is keyword for value type predicate:
1type Falsy = false | '' | 0 | null | undefined;
2const isFalsy = (val: unknown): val is Falsy => !val;
1function isNotNullish<T>(value: T): value is NonNullable<T> {
2 return value !== undefined && value !== null;
3}
4
5// %inferred-type: (number | null | undefined)[]
6const mixedValues = [1, undefined, 2, null];
7
8// %inferred-type: number[]
9const numbers = mixedValues.filter(isNotNullish);
1/**
2 * A partial implementation of the `typeof` operator.
3 */
4function isTypeof(value: any, typeString: 'boolean'): value is boolean;
5function isTypeof(value: any, typeString: 'number'): value is number;
6function isTypeof(value: any, typeString: 'string'): value is string;
7function isTypeof(value: any, typeString: string): boolean {
8 return typeof value === typeString;
9}
10
11const value: unknown = {};
12
13if (isTypeof(value, 'boolean')) {
14 // %inferred-type: boolean
15 console.log(value);
16}

Type Assertion

  • <type>.
  • as type.
as is better in .jsx
1let foo: any;
2const bar = foo as string; // 现在 bar 的类型是 'string'
1function handler(event: Event) {
2 const mouseEvent = event as MouseEvent;
3}

Type System

TypeScript type system:
  • Turing complete type system.
  • Structural type system: type checking focuses on shape (Duck Typing).

Covariant

Covariant (协变性):
Type T is covariant if having S <: P, then T<S> <: T<P>.
1type IsSubtype<S, P> = S extends P ? true : false;
2
3type T1 = IsSubtype<Admin, User>;
4// type T1 = true
5
6type T2 = IsSubtype<Promise<Admin>, Promise<User>>;
7// type T2 = true
8
9type T3 = IsSubtype<'Hello', string>;
10// type T3 = true
11
12type T4 = IsSubtype<Capitalize<'Hello'>, Capitalize<string>>;
13// type T4 = true

Contravariant

Contravariant (逆变性):
Type T is contravariant if having S <: P, then T<P> <: T<S>.
1type IsSubtype<S, P> = S extends P ? true : false;
2
3type Func<Param> = (param: Param) => void;
4
5type T1 = IsSubtype<Admin, User>;
6// type T1 = true
7
8type T2 = IsSubtype<Func<Admin>, Func<User>>;
9// type T2 = false
10
11type T3 = IsSubtype<Func<User>, Func<Admin>>;
12// type T3 = true
1const logAdmin: Func<Admin> = (admin: Admin): void => {
2 console.log(`Name: ${admin.userName}`);
3 console.log(`Is super admin: ${admin.isSuperAdmin.toString()}`);
4};
5
6const logUser: Func<User> = (user: User): void => {
7 console.log(`Name: ${user.userName}`);
8};
9
10const admin = new Admin('admin1', true);
11
12let logger: Func<Admin>;
13
14logger = logUser;
15logger(admin); // OK
16
17logger = logAdmin;
18logger(admin); // OK
19
20const user = new User('user1');
21
22let logger: Func<User>;
23
24logger = logUser;
25logger(user); // OK
26
27logger = logAdmin;
28// Type 'Func<Admin>' is not assignable to type 'Func<User>'.
29// Property 'isSuperAdmin' is missing in type 'User' but required in type 'Admin'.
30logger(user); // Oops! `user.isSuperAdmin` is undefined.

Type Gymnastics

LevelEnvironmentOperandsOperations
Program levelRuntimeValuesFunctions
Type levelCompile timeSpecific typesGeneric types
Copyright © Sabertaz Built with React and NextLast Built Time