概述
- 类型就是一组值的集合
类型擦除
TS 转化成 JS
方法
-
npm i -g esbuild
=>esbuild 1.ts > 1.js
=> 快(不检查 TS 语法) -
npm i -g @swc/cli @swc/core
=>swc 1.ts -o 1.js
=> 快(不检查 TS 语法) -
npm i -g typescript
=>tsc 1.ts
数据类型
- null/undefined
- string/number/boolean
- bigint/symbol
- object
- void
- any/unknow
- never
- enum
- 自定义类型 => type(类型别名)/interface
TS 描述对象
- 使用 class/constructor 描述
- 使用 type/interface 描述
type A = {
// 索引签名
[key: string]: number
}
// Record 泛型
type B = Record<string, number>;
const n: BigInt = 100n;
console.log(n); // 100n
const symbol: Symbol = Symbol("s");
console.log(symbol); // Symbol(s)
const date: Date = new Date()
const regexp: RegExp = /ab+c/;
const regexp1: RegExp = new RegExp("/\d+/");
const map: Map<string, number> = new Map();
map.set("a", 1);
const set: Set<number> = new Set();
set.add(1);
TS 描述数组
-
type A = string[]
==type A = Array<string>
- 元组 =>
type A = [string, string, number]
=> length == 3 -
type A = [1, 2, 3 | 4]
=>const a: A = [1, 2, 3]
TS 描述函数
-
type FnA = (a: number, b: number) => string
=>const a: FnA = () => "hello world";
type FnReturnVoid = () => void;
type FnReturnUndefined = () => undefined;
const f1: FnReturnVoid = () => {
console.log("hi");
}
const f2: FnReturnUndefined = () => {
console.log("hi");
// 此处需要显式声明
return undefined;
}
type Person = {
name: string,
age: number,
sayHi: FnWithThis
}
type FnWithThis = (this: Person, name: string) => void;
// 此处如果使用箭头函数,则 this 指向有问题
const sayHi: FnWithThis = function () {
console.log("hi " + this.name)
}
const ming: Person = {
name: "ming",
age: 18,
sayHi: sayHi
}
ming.sayHi("Jack");
sayHi.call(ming, "Jack");
any vs unknown
- any => 全集 => 不做任何类型检查
- any 不等于所有类型的联合 => 反证法:联合类型如果没有区分类型,只能使用交集的属性|方法
- TS 绝大部分规则对 any 不生效 =>
const fn = (a: any) => {const b: never = a;}
=> 报错 - unknown => 未知集 => 使用时需要先收窄类型 => 所有类型的联合(联合类型)
never
- 空集
- 不应该出现的类型
enum
- 使用一个有意义的名字代表一个状态
enum A {
TODO = 0,
DONE,
ARCHIVED,
DELETED
}
// 类型擦除时 A.DONE 会变成 1
let status: A = A.DONE;
- 通过使用位运算可以很方便的将权限进行组合和对比
enum Permission {
NONE = 0, // 0000
READ = 1 << 0, // 0001
WRITE = 1 << 1, // 0010
DELETE = 1 << 2, // 0100
MANAGE = READ | WRITE | DELETE // 0111
}
type User = {
permission: Permission
}
const user: User = {
permission: 0b0011
}
if ((user.permission & Permission.WRITE) === Permission.WRITE) {
console.log("拥有写权限")
}
- 使用 enum 做字符串映射字符串时可以使用类型别名代替
// enum Status {
// done = 'done',
// process = 'process',
// start = 'start'
// }
type Status = 'done' | 'process' | 'start';
type
- 类型别名
- 声明一个别名,并不是一个新的类型
-
type 的值都是类型 => type 的值里面不能有任何值 =>
type nan = NaN // 这是错误的
=> 'NaN' refers to a value, but is being used as a type here -
type A = {name: string}
=> 表示 name 为 string 的对象
type A = { name: string };
const a1 = { name: 'ming', age: 18 };
// 此时 TS 不会做严格检查
const a2: A = a1;
// Error => TS 第一次声明的时候会进行严格检查
const a: A = { name: 'ming', age: 18 }
// 其中的 0、null、undefined、''、false 都是类型
type Falsy = 0 | null | undefined | '' | false;
// 类型A是一个集合,集合中只有一个0
type A = 0;
type FnWithProp = {
(a: number, b: number): number,
props1: number;
}
const fn: FnWithProp = (x, y) => x + y;
fn.props1 = 101;
console.log(fn);
console.log(fn.props1)
interface
- 声明接口
- 描述对象的属性 => declare the shapes of object
type vs interface
- interface 只描述对象,type 则描述所有类型
- interface 是类型声明,type 是别名
type A = string; type B = A; // B is string interface C extends Date { } type D = C; // D is C
- interface 可以进行 merging(扩展),type 不可以重新赋值 => 对外 API 尽量使用 interface,方便扩展。对内 API 尽量使用 type(case: props),防止代码分散
- interface extends 时如果属性冲突,直接报错,type 会联合类型到 never
Object vs object
- Object 包含 Number/String/Boolean 包装类型
- object 不包含包装类型
void vs null vs undefined
- void 不可以赋值给 null 和 undefined
- undefined 可以赋值给 void
- 其余需要看配置 => strictNullChecks
let a: void
// 此时 void = null | undefined
if (typeof a === 'object') {
// a is null
console.log(a);
} else if (typeof a === 'undefined') {
// a is undefined
console.log(a);
} else {
// a is never
console.log(a);
}
// 此时 void = undefined
if (typeof a === 'undefined') {
// a is undefined
console.log(a);
} else if (typeof a === 'object') {
// a is never
console.log(a);
} else {
// a is never
console.log(a);
}
Assignment
TS 类型系统运算
联合类型
- union types => 并集
JS 语言特性区分类型
- 使用 JS 的语言特性 typeof + instanceof + in 进行类型收窄
const fn = (a: number | string) => {
// 类型收窄 => Narrowing
if (typeof a === 'number') {
a.toFixed(2);
} else if (typeof a === 'string') {
a.split(',');
} else {
// a === never
console.log(a);
}
}
type Person = {
name: string;
}
type Animal = {
x: string;
}
const f1 = (param: Person | Animal) => {
// 使用 in 进行类型收窄
if ('name' in param) {
console.log(param.name);
} else {
console.log(param.x);
}
}
类型谓词 is
type Rect = {
height: number,
width: number
}
type Circle = {
center: [ number, number ],
radius: number
}
const fn = (a: Rect | Circle) => {
if (isRect(a)) {
// a is Rect
console.log(a.height);
} else if (isCircle(a)) {
// a is Circle
console.log(a.radius);
} else {
// a is never
console.log(a);
}
}
function isRect(x: Rect | Circle): x is Rect {
return 'height' in x && 'width' in x;
}
// 此处 x is Circle 不能写在左侧 => const isCircle: (x: Rect | Circle) => x is Circle = x => 'radius' in x && 'center' in x;
const isCircle = (x: Rect | Circle): x is Circle => {
return 'radius' in x && 'center' in x;
}
可辨别联合 Discriminated Unions
- 让复杂类型的收窄变成简单类型的对比
type Circle = { kind: "Circle", center: [ number, number ] };
type Square = { kind: "Square", sideLength: number };
type Shape = Circle | Square;
const f1 = (a: string | Shape) => {
if (typeof a === 'string') {
// a is string
a.split(',');
} else if (a.kind === 'Circle') {
// a is Circle
console.log(a.center);
} else {
// a is Square
console.log(a.sideLength);
}
}
断言 as
// a is string
let a = 'hi';
// b is hi
const b = 'hi';
// c is hi
let c = 'hi' as const;
// array is (number | string)[] => 对于引用类型在 TS 中 const 相当于 let
const array = [ 1, 'hi' ];
// array1 is readonly [1, 'hi']
const array1 = [ 1, 'hi' ];
// Error: Property 'push' does not exist on type 'readonly [1, "hi"]'.
array1.push(2);
交叉类型 intersection types
- 交叉类型常用于有交集的类型 A B
- 如果 A、B 无交集,可能得到 never,也可能属性为 never
type
type Left = {
left: string;
}
type Right = {
right: string;
}
type C = Left | Right;
type D = Left & Right;
const c: C = {
left: "left"
}
const d: D = {
left: 'left',
right: 'right'
}
type Person = {
name: string;
age: number;
id: string
}
// 此处 id 属性的类型是 string & number => id is never
type User = Person & {
id: number;
email: string;
}
const user: User = {
id: 1 as never,
name: 'ming',
age: 18,
email: 'email'
}
// userIsNever is never
type UserIsNever = { id: 'A' } & { id: 'B' };
interface
interface Colorful {
color: string;
}
interface Circle {
radius: number
}
type ColorfulCircle = Colorful & Circle;
interface Person {
id: string;
name: string;
}
interface User extends Person {
// Error: type 'number' is not assignable to type 'string'.
// id: number;
email: string;
}
神奇
type A = {
method: (n: number) => void;
}
type B = {
method: (n: string) => void;
} & A;
const b: B = {
// n is any
method: (n) => {
console.log(n);
}
}
type F1 = (n: number) => void;
type F2 = (n: string) => void;
type X = F1 & F2;
const x: X = (n) => {
// n is any
console.log(n);
n.split(',')
n.toFixed(2);
}
类型兼容 & 赋值
基本类型
type A = string | number;
const a: A = 'hi';
普通对象
type Person = {
name: string;
age: number;
}
let user = { name: 'ming', age: 18, id: 1 };
let p: Person;
p = user;
// type User = {name: string; age: number; id: nubmer}
type User = typeof user;
接口
interface Parent {
x: string;
}
interface Sub extends Parent {
y: string;
}
let sub: Sub = {
x: '1',
y: '2'
};
let p: Parent;
p = sub;
函数
- 函数包括参数和返回值
- 参数少的函数可以赋值给参数多的函数
- 对参数要求少函数可以赋值给对参数要求多的函数
深入对象语法
索引签名 Index Signature
type Hash = {
[k: string]: unknown;
length: number;
}
映射类型 Mapped Type
- 多用于泛型
type Hash = {
// 不能再写其他属性
[key in string]: unknown;
// length: number; => error
}
只读属性 readonly
interface User {
readonly id: number;
age?: number;
}
const user: User = {
id: 1,
age: 18,
}
// Error: Cannot assign to 'id' because it is a read-only property
user.id = 2;
深入函数语法
对象语法全部适用于函数
type F = {
(a: number, b: number): number;
readonly count?: number;
}
type F1 = (a: number, b: number) => number;
const f: F = (x, y) => x + y;
声明函数及其类型
// First
type F1 = (a: number, b: number) => number;
const f1: F1 = (a, b) => a + b;
// Second
const f2 = (a: number, b: number): number => a + b;
type F2 = typeof f2;
// Third
function f3(this: unknown, a: number, b: number): number {
return a + b;
}
type F3 = typeof f3;
// Fourth
const f4 = function (this: unknown, a: number, b: number): number {
return a + b;
}
type F4 = typeof f4;
类型谓词
可选参数
参数默认值
function addEventListener(eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture = false) {
const element = {} as HTMLElement;
const event = {} as Event;
fn.call(element, event);
}
addEventListener('click', () => console.log(1));
重载 overload
- overload => 同名函数参数不同
- 参数类型不同 => 联合类型
- 参数个数不同 => 考虑是用 overload
- 参数类型和个数都不同
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(a: number, b?: number, c?: number): Date {
if (a !== undefined && b !== undefined && c !== undefined) {
return new Date(a, b, c);
} else if (a !== undefined && b === undefined && c === undefined) {
return new Date(a);
} else {
throw new Error('Params is invalid!');
}
}
// Error: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
createDate(2022, 10);
展开参数
function sum(...array: number[]) {
// Error: Argument of type 'number[]' is not assignable to parameter of type 'number'.
// f(array)
f(...array);
f.apply(null, array);
}
function f(...array: number[]) {
console.log(array);
}
as const
function add(a: number, b: number): number {
return a + b;
}
// arr is number[]
const arr = [ 1, 2 ];
// Error: A spread argument must either have a tuple type or be passed to a rest parameter.
add(...arr);
// arr1 is readonly [1, 2]
const arr1 = [ 1, 2 ] as const;
add(...arr1);
解构
type Config = {
url: string;
method: "GET" | "POST" | "PUT" | "DELETE";
body?: unknown;
headers?: unknown
}
// rest is {body?: unknown; headers?: unknown}
function ajax({ url, method, ...rest }: Config = { url: '', method: "GET" }) {
}
function ajax1({ url, method, ...rest } = { url: '', method: "GET" } as Config) {
}
泛型 Generic Type
- 推后执行的、部分待定的类型
type F<A, B> = A | B;
// Result is number | string
type Result = F<number, string>;
function echo(msg: number | boolean | string) {
return msg;
}
// F is (msg: number | boolean | string) => number | boolean | string;
type F = typeof echo;
类型参数默认值
interface Hash<V = string> {
[k in string]: V;
}
条件类型 Conditional Type
- 以下两条仅对泛型有效
- 如果 T 是 never,则表达式的值为 never
- 如果 T 是联合类型,则分开计算
type LikeString<T> = T extends string ? true : false;
type R1 = LikeString<'hi'>; // true
type R2 = LikeString<true>; // false
type X = LikeString<never>; // never
type Y = LikeString<string | number>; // Y is boolean
type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<number | string>; // Result is number[] | string[]
type Result1 = ToArray<never>; // Result1 is never
type Z = never extends unknown ? 1 : 2; // Z is 1
// 此处不能对应 string -> string[] number -> number[]
type U = string | number extends unknown ? string[] | number[] : 1;
keyof
type Person = { name: string, age: number };
type GetKeys<T> = keyof T;
type Result = GetKeys<Person>; // Result is name | age
泛型约束
type GetKeyType<T, K extends keyof T> = T[K];
type Result1 = GetKeyType<Person, "name">; // string
type Test = Person['name']; // Test is string
内置
Readonly
type Person = { name: string, age: number };
// type Result = {
// readonly name: string;
// readonly age: number;
// }
type Result = Readonly<Person>;
// type Readonly<T> = {
// readonly [k in keyof T]: T[k];
// }
Partial
type Person = { name: string, age: number };
// type Result = {
// name?: string | undefined;
// age?: number | undefined;
// }
type Result = Partial<Person>;
// type Partial<T> = {
// [k in keyof T]?: T[k];
// }
Required
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: string;
// age: number;
// email: string;
// }
type Result = Required<Person>;
// type Required<T> = {
// [k in keyof T]-?: T[k];
// }
Record
type Result = Record<string, number>;
// type Record<K extends string | number | symbol, T> = {
// [k in K]: T;
// }
Exclude
type Result = Exclude<1 | 2 | 3, 1 | 2>; // Result is 3
// type Exclude<T, K> = T extends K ? never : T;
Extract
type Result = Extract<1 | 2 | 4, 2 | 3>; // Result is 3
// type Extract<T, K> = T extends K ? T : never;
Pick
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: string | number;
// age: string | number;
// }
type Result = Pick<Person, 'name' | 'age'>;
// type Pick<T, K extends keyof T> = {
// [key in K]: T[K];
// }
Omit
type Person = { name: string, age: number, email?: string };
// type Result = {
// email?: string | undefined;
// }
type Result = Omit<Person, 'name' | 'age'>;
// type Omit<T, K> = {
// [k in keyof T as (k extends K ? never : k)]: T;
// }
// 使用 Pick
// type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
-readonly
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: Readonly<Person>;
// age: Readonly<Person>;
// email?: Readonly<Person> | undefined;
// }
type Result = Mutable<Readonly<Person>>;
type Mutable<T> = {
-readonly [key in keyof T]: T;
}
class
class Point {
x: number;
y: number;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
// 等价于
class Point1 {
constructor(public x = 0, public y = 0) {
}
}
class Hash {
[key: string]: unknown;
set(key: string, value: unknown) {
this[key] = value;
}
get(key: string) {
return this[key];
}
}
constructor reload
class Point {
x!: number;
y!: number;
constructor(x: number, y: number);
constructor(str: string);
constructor(xs: number | string, y?: number) {
if (typeof xs === 'number' && typeof y == 'number') {
this.x = xs;
this.y = y;
} else if (typeof xs === 'string') {
this.x = parseFloat(xs.split(',')[0]);
this.x = parseFloat(xs.split(',')[1]);
}
}
}
implements
- implements 仅仅会做类型检查,其余不会进行任何操作
interface Person {
name: string;
sayHi: (target: Person) => void;
}
interface Taggable {
tags: string[];
addTag: (tag: string) => void;
removeTag: (tag: string) => void;
}
class User implements Person, Taggable {
constructor(public name: string, public tags: string[] = []) {
}
sayHi(target: Person) {
console.log('Hi ' + target.name);
}
addTag(tag: string) {
this.tags.push(tag);
}
removeTag(tag: string) {
this.tags.splice(this.tags.indexOf(tag), 1);
}
}
const ming = new User('ming');
ming.sayHi(ming);
declare
- 重写属性
class Person {
friend?: Person;
constructor(public name: string, friend?: Person) {
this.friend = friend;
}
}
class User extends Person {
// Error: Property 'friend' will overwrite the base property in 'Person'. If this is intentional, add an initializer. Otherwise, add a 'declare' modifier or remove the redundant declaration.
// friend?: User;
declare friend?: User;
constructor(public id: string, name: string, friend?: User) {
super(name, friend);
}
}
const u1 = new User("1", "jack");
const u2 = new User("2", "george", u1);
// 如果没有 declare => Friend is Person | undefined
// 如果有 declare => Friend is User | undefined
type Friend = typeof u2.friend;
public & private & protected
- public => 类外可见
- private => 类内可见 => 类型擦除之后就没有 private 了,在 js 中仍然可以使用该属性
- protected => 子类可见
-
#<var>
=> 真私有属性 => 类型擦除之后会保留,在 js 中不可使用该属性
static
- 通过类型访问
- 类属性/静态属性
- 不能有 static name => 类是使用函数模拟的,函数本身有 name 属性
- static block
class Foo {
static #count = 0;
static {
const count = loadFromLocalStorage() || 0;
Foo.#count += count;
}
constructor() {
console.log(Foo.#count);
}
}
抽象类
- 抽象类不能直接实例化
abstract class A {
abstract name: string;
age: string;
constructor(age: string) {
this.age = age;
}
abstract getName(): string;
printName() {
console.log("Hi, " + this.getName());
}
}
类作为参数
class Person {
constructor(public name: string) {
this.name = name;
}
}
function f(X: typeof Person) {
const p = new X('George');
}
f(Person)
// new 表示传入的参数必须是 class
function f2(X: new (name: string) => Person) {
const p = new X("George");
}
f2(Person);
类型体操
// if (A <= B) true else false
type A = 1;
type B = 1 | 2;
type Result = A extends B ? true : false;
// 空元组
type A = [];
type IsEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0 ? true : false;
type Result = IsEmptyArray<[]>;
// 非空元组
type B = [ 1 ];
type NotEmpty<Arr extends unknown[]> = Arr extends [ ...unknown[], unknown ]
? true
: false;
type NotEmpty1<Arr extends unknown[]> = Arr extends [ ...infer X, infer Last ]
? true
: false;
type Result1 = NotEmpty<B>;
递归
- TS 最多递归 48 层
type A = [ 1, 2, 3, 4 ];
type Reverse<Arr extends unknown[]> = Arr extends [ ...infer Rest, infer Last ] ? [ Last, ...Reverse<Rest> ] : Arr;
type Result = Reverse<A>;
模式匹配 + infer
- infer => decoratively introduce a new generic type variable
type A = [ 1, 2, 3, 4 ];
// Result1 is 1
type Result1 = A extends [ infer First, ...infer Rest ] ? First : never;
// Result2 is [2, 3, 4]
type Result2 = A extends [ string, ...infer Rest ] ? Rest : never;
元组体操
// 将元组加长
type A = [ 1 ];
type B = [ ...A, number ];
type C = [ ...B, 'hi' ];
type D = [ ...B, ...C ];
// get Last type in Tuple
type Last<T extends unknown[]> = T extends [ ...unknown[], infer Last ] ? Last : never;
type Hi = Last<C>; // Hi is 'hi'
// Error => TS 没有减法
// type Last<T extends unknown[]> = T[T["length"] - 1];
字符串体操
Capitalize & Uncapitalize & Uppercase & Lowercase
- TS 内部实现
- Capitalize => 首字母大写
- Uncapitalize => 首字母小写
- Uppercase => 全部大写
- Lowercase => 全部小写
type A = "george";
type B = Capitalize<A>; // B is George
type C = 'hi' | 'george';
type D = Capitalize<C>; // D is 'Hi' | 'George'
模板字符串
type A = "Hi";
type B = "George";
type Result = `${A} ${B}`;
模式匹配
type A = "Hi George";
type First<T extends string> = T extends `${infer F}${string}` ? F : never;
type A = "Hi George";
type LastOfTuple<T extends unknown[]> = T extends [ ...infer _, infer L ] ? L : never;
// String to Tuple
type StringToTuple<S extends string> = S extends `${infer F}${infer Rest}` ? [ F, ...StringToTuple<Rest> ] : [];
type Last<T extends string> = LastOfTuple<StringToTuple<T>>;
type Result = First<A>; // Result is H
type ResultOfLast = Last<A>; // Result is e
String to Union
- 联合类型自动去重
type A = "Hi George";
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}` ? First | StringToUnion<Rest> : never;
type Result = StringToUnion<A>;
TSX
- 在 TSX 中标签式断言会和标签冲突,此时使用 as 断言
知识点
- 在 TSX 中 input 的 onChange 实际上调用的是 oninput 事件
- onchange => 触发时机是失去焦点时
- oninput => 触发时机是输入时