TypeScript Basic Notes
Modules
Classic Module Resolution
import { a } from './module'
:
import { a } from 'module'
:
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
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
17function toGerman(value) {
18 switch (value) {
19 case 'No' :
20 return 'Neither';
21 case 'Yes' :
22 return 'Ja';
23 }
24}
Non-const enums are objects:
1
2enum Tristate {
3 False,
4 True,
5 Unknown,
6}
7
8
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]);
17console.log(Tristate.False);
18console.log(Tristate[Tristate.False]);
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
9
10
11
12
13type Age = typeof MyArray[number]['age'];
14
15
16type Age2 = Person['age'];
17
{ [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};
Template Literal Types
Based on literal types.
4 intrinsic String Manipulation Types:
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
9
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
22 console.log(`new name is ${newName.toUpperCase()}`);
23});
24
25person.on('ageChanged', newAge => {
26
27 if (newAge < 0) {
28 console.warn('warning! negative age');
29 }
30});
31
32
33person.on('firstName', () => {});
34
35
36
37person.on('fstNameChanged', () => {});
38
39
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>();
10const stringList = createList<string>();
11const arrayList = createList<any[]>();
12const boolList = createList<boolean>();
在类型编程里, 泛型就是变量:
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
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
2type T1 = TypeName<string | (() => void)>;
3
4
5type T2 = TypeName<string | string[]>;
6
7
8type T3 = TypeName<string[] | number[]>;
1type Naked<T> = T extends boolean ? 'Y' : 'N';
2type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N';
3
4
5
6
7
8type Distributed = Naked<number | boolean>;
9
10
11
12
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
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
13
14
15
1
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
16
17
18
19
1
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
13
14
1
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
13
14
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
2
3type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
4
5
6type A = Awaited<Promise<string>>;
7
8
9type B = Awaited<Promise<Promise<number>>>;
10
11
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
6type FooReturnType = ReturnType<typeof foo>;
Type Guards
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
17 return attendee.teacherId;
18 case 'Student':
19
20 return attendee.studentId;
21 default:
22 throw new Error('Unsupported type');
23 }
24}
Never Type Guard
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
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
13
14 throw new UnsupportedValueError(value);
15 }
16}
Type Predicates
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
6const mixedValues = [1, undefined, 2, null];
7
8
9const numbers = mixedValues.filter(isNotNullish);
1
2
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
15 console.log(value);
16}
Type Assertion
as
is better in .jsx
1let foo: any;
2const bar = foo as string;
1function handler(event: Event) {
2 const mouseEvent = event as MouseEvent;
3}
Type System
TypeScript type system:
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
5
6type T2 = IsSubtype<Promise<Admin>, Promise<User>>;
7
8
9type T3 = IsSubtype<'Hello', string>;
10
11
12type T4 = IsSubtype<Capitalize<'Hello'>, Capitalize<string>>;
13
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
7
8type T2 = IsSubtype<Func<Admin>, Func<User>>;
9
10
11type T3 = IsSubtype<Func<User>, Func<Admin>>;
12
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);
16
17logger = logAdmin;
18logger(admin);
19
20const user = new User('user1');
21
22let logger: Func<User>;
23
24logger = logUser;
25logger(user);
26
27logger = logAdmin;
28
29
30logger(user);
Type Gymnastics
Level | Environment | Operands | Operations |
---|
Program level | Runtime | Values | Functions |
Type level | Compile time | Specific types | Generic types |