前言
用了很久的 typescript,用了但感觉又没完全用。因为很多 typescript 的特性没有被使用,查看之前写的代码满屏的 any,这样就容易导致很多 bug,也没有发挥出 typescript 真正的“类型”威力。本文总结了一些使用 typescript 的小技巧,以后使用 typescript 时可以运用起来。
废话不多说,直接上代码。
函数重载
当希望传 user 参数时,不传 flag,传 para 时,传 flag。就可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
interface User { name: string;
age: number;
} const user = { name: 'Jack' ,
age: 123
}; class SomeClass { public test(para: User): number;
public test(para: number, flag: boolean): number;
public test(para: User | number, flag?: boolean): number {
// 具体实现
return 1;
}
} const someClass = new SomeClass();
// ok someClass.test(user); someClass.test(123, false );
// Error // someClass.test(123); //Argument of type 'number' is not assignable to parameter of type 'User'. // someClass.test(user, false); //Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'number'. |
映射类型
在了解映射类型之前,需要了解 keyof, never, typeof, in。
keyof:keyof 取 interface 的键
1
2
3
4
5
6
7
|
interface Point { x: number;
y: number;
} // type keys = "x" | "y" type keys = keyof Point; |
never:永远不存在的值的类型
官方描述:
the never type represents the type of values that never occur.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 例子:进行编译时的全面的检查 type Foo = string | number; function controlFlowAnalysisWithNever(foo: Foo) {
if ( typeof foo === "string" ) {
// 这里 foo 被收窄为 string 类型
} else if ( typeof foo === "number" ) {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
} |
使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
typeof:取某个值的 type
1
2
3
4
|
const a: number = 3 // 相当于: const b: number = 4 const b: typeof a = 4
|
in:检查一个对象上是否存在一个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
interface A { x: number;
} interface B { y: string;
} function doStuff(q: A | B) {
if ( 'x' in q) {
// q: A
} else {
// q: B
}
} |
映射类型就是将一个类型映射成另外一个类型,简单理解就是新类型以相同的形式去转换旧类型的每个属性。
Partial, Readonly, Nullable, Required
- Partial 将每个属性转换为可选属性
- Readonly 将每个属性转换为只读属性
- Nullable 转换为旧类型和null的联合类型
- Required 将每个属性转换为必选属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
type Partial<T> = { [P in keyof T]?: T[P];
} type Readonly<T> = { readonly [P in keyof T]: T[P];
} type Nullable<T> = { [P in keyof T]: T[P] | null
} type Required<T> = { [P in keyof T]-?: T[P]
} interface Person { name: string;
age: number;
} type PersonPartial = Partial<Person>; type PersonReadonly = Readonly<Person>; type PersonNullable = Nullable<Person>; type PersonPartial = { name?: string | undefined;
age?: number | undefined;
} type PersonReadonly = { readonly name: string;
readonly age: number;
} type PersonNullable = { name: string | null ;
age: number | null ;
} interface Props { a?: number;
b?: string;
} const obj: Props = { a: 5 }; const obj2: Required<Props> = { a: 5 }; // Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'. |
Pick, Record
- Pick 选取一组属性指定新类型
- Record 创建一组属性指定新类型,常用来声明普通Object对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
type Pick<T, K extends keyof T> = { [P in K]: T[P];
} type Record<K extends keyof any, T> = { [P in K]: T;
} interface Todo { title: string;
description: string;
completed: boolean;
} type TodoPreview = Pick<Todo, "title" | "completed" >;
const todo: TodoPreview = { title: "Clean room" ,
completed: false ,
}; todo; // = const todo: TodoPreview
interface PageInfo { title: string;
} type Page = "home" | "about" | "contact" ;
const nav: Record<Page, PageInfo> = { about: { title: "title1" },
contact: { title: "title2" },
home: { title: "title3" },
}; nav.about; // = const nav: Record
|
Exclude, Omit
- Exclude 去除交集,返回剩余的部分
- Omit 适用于键值对对象的Exclude,去除类型中包含的键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Exclude<T, U> = T extends U ? never : T type Omit = Pick<T, Exclude<keyof T, K>> // 相当于: type A = 'a' type A = Exclude< 'x' | 'a' , 'x' | 'y' | 'z' >
interface Todo { title: string;
description: string;
completed: boolean;
} type TodoPreview = Omit<Todo, "description" >;
const todo: TodoPreview = { title: "a" ,
completed: false ,
}; |
ReturnType
获取返回值类型,一般为函数
1
2
3
4
5
6
7
8
9
|
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
declare function f1(): { a: number; b: string };
type T1 = ReturnType< typeof f1>;
// type T1 = { // a: number; // b: string; // } |
还有很多映射类型,可查看Utility Types参考。
类型断言
类型断言用来明确的告诉 typescript 值的详细类型,合理使用能减少我们的工作量。
比如一个变量并没有初始值,但是我们知道它的类型信息(它可能是从后端返回)有什么办法既能正确推导类型信息,又能正常运行了?有一种网上的推荐方式是设置初始值,然后使用 typeof 拿到类型(可能会给其他地方用)。也可以使用类型断言可以解决这类问题:
1
2
3
4
5
6
7
8
|
interface User { name: string;
age: number;
} export default class someClass {
private user = {} as User;
} |
枚举
枚举类型分为数字类型与字符串类型,其中数字类型的枚举可以当标志使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
enum AnimalFlags { None = 0,
HasClaws = 1 << 0,
CanFly = 1 << 1,
HasClawsOrCanFly = HasClaws | CanFly
} interface Animal { flags: AnimalFlags;
[key: string]: any;
} function printAnimalAbilities(animal: Animal) {
var animalFlags = animal.flags;
if (animalFlags & AnimalFlags.HasClaws) {
console.log( 'animal has claws' );
}
if (animalFlags & AnimalFlags.CanFly) {
console.log( 'animal can fly' );
}
if (animalFlags == AnimalFlags.None) {
console.log( 'nothing' );
}
} var animal = { flags: AnimalFlags.None };
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws; printAnimalAbilities(animal); // animal has claws
animal.flags &= ~AnimalFlags.HasClaws; printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly; printAnimalAbilities(animal); // animal has claws, animal can fly
|
- 使用 |= 来添加一个标志;
- 组合使用 &= 和 ~ 来清理一个标志;
- | 来合并标志。
这个或许不常用,在 typescript 关于 types 源码中我们也可以看到类似的代码:
字符串类型的枚举可以维护常量:
1
2
3
4
5
6
7
8
9
|
const enum TODO_STATUS { TODO = 'TODO' ,
DONE = 'DONE' ,
DOING = 'DOING'
} function todos (status: TODO_STATUS): Todo[];
todos(TODO_STATUS.TODO) |
元组
表示一个已知元素数量和类型的数组,各元素的类型不必相同。
1
2
|
let x: [string, number]; x = [ 'hello' , 10];
|
在发出不固定多个请求时,可以应用:
1
2
3
4
5
|
if (flag) {
} const [ { data: a }, response ] = await Promise.all(requestList) as [Response<A>, Response<B>?] |
范型
在定义泛型后,有两种方式使用,一种是传入泛型类型,另一种使用类型推断。
1
2
3
|
declare function fn<T>(arg: T): T; // 定义一个泛型函数
const fn1 = fn<string>( 'hello' ); // 第一种方式,传入泛型类型
string const fn2 = fn(1); // 第二种方式,从参数 arg 传入的类型 number,来推断出泛型 T 的类型是 number
|
一个扁平数组结构建树形结构例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 转换前数据 const arr = [ { id: 1, parentId: 0, name: 'test1' },
{ id: 2, parentId: 1, name: 'test2' },
{ id: 3, parentId: 0, name: 'test3' }
]; // 转化后 [ { id: 1, parentId: 0, name: 'test1' ,
childrenList: [ { id: 2, parentId: 1, name: 'test2' , childrenList: [] } ] },
{ id: 3, parentId: 0, name: 'test3' , childrenList: [] }
] interface Item { id: number;
parentId: number;
name: string;
} // 传入的 options 参数中,得到 childrenKey 的类型,然后再传给 TreeItem interface Options<T extends string> { childrenKey: T;
} type TreeItem<T extends string> = Item & { [key in T]: TreeItem<T>[] | [] };
declare function listToTree<T extends string = 'children' >(list: Item[], options: Options<T>): TreeItem<T>[];
listToTree(arr, { childrenKey: 'childrenList' }).forEach(i => i.childrenList)
|
infer
表示在 extends 条件语句中待推断的类型变量。
1
|
type ParamType<T> = T extends (param: infer P) => any ? P : T; |
这句话的意思是:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。
1
2
3
4
5
6
7
|
interface User { name: string;
age: number;
} type Func = (user: User) => void type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
|
例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
// [string, number] -> string | number type ElementOf<T> = T extends Array<infer E> ? E : never; type TTuple = [string, number]; type ToUnion = ElementOf<TTuple>; // string | number
// T1 | T2 -> T1 & T2 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Result = UnionToIntersection<T1 | T2>; // T1 & T2
|
总结
typescript 关于类型限制还是非常强大的,由于短链接文章有限,还有其他类型比如联合类型,交叉类型等读者可自行翻阅资料查看。刚开始接触范型以及其各种组合会感觉不熟练,接下来在项目中会慢慢应用,争取将 bug 降至最低限度。