0%

TypeScript技巧之从入门到装逼

预备动作

阅读本篇之前你可能需要一定的TypeScript基础,如果还没开始上手建议从这里开始

以下是传送门

  1. TypeScript Document 官方文档
  2. TypeScript HandBook 小手册
  3. TypeScript Playground 旧版练习场
  4. TypeScript Playground 新版练习场 (比上面那个高级,写作期间是beta版)

正片开始

主要介绍 TypeScript 的常用和不常用的各种技巧,包括用这些技巧创建一些高级类型函数,会附上 Playground 的链接。

关键词

用于更加细粒度的处理接口和类型别名**

  • @scope runtime 运行时
  • @scope lexical 静态分析/词法分析

类型断言 / 类型守卫 / 类型约束

可以获取,确定,约束变量类型

typeof @scope runtime

获取变量的类型,常用于一些需要确认变量的类型然后进行不同操作的场景

1
2
3
4
5
6
7
type AOrB = { s: string; } | string;
const val: AOrB = { s: 'a' };
if (typeof val === 'object') {
console.log(val.s);
} else {
console.log(val);
}

Playground Link

extends @scope lexical

类型约束,通常用在泛型传参时约束参数范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Gen {
s: string;
}

interface Others {
d: number;
}

function BaseFunc<P extends Gen>(p: P) {
console.log(p);
}

const s1: Gen = {
s: 'something',
};

const s2: Others = {
d: 1,
};

BaseFunc(s1); // good

BaseFunc(s2); // bad, error

Playground Link

instanceof @scope runtime

确认目标是是否在指定对象的原型链上

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
50
51
52
class Animal {
name: string;

constructor(name: string) {
this.name = name;
}
}

class Dog extends Animal {
wang: number;

constructor(name: (typeof Animal)['name'], wang: number) {
super(name);
this.wang = wang;
}

wangwang() {
console.log('wangwang');
}
}

class Cat extends Animal {
miao: number;

constructor(name: (typeof Animal)['name'], miao: number) {
super(name);
this.miao = miao;
}

miaomiao() {
console.log('miaomiao');
}
}

const dog: Dog = new Dog('dog', 1);

const cat: Cat = new Cat('cat', 1);

function doS<C extends Animal>(cls: C) {
/** bad */
cls.wangwang(); // error
cls.miaomiao(); // error
/** good */
/** 函数已经限制了 cls 的约束范围 */
if (cls instanceof Dog) {
/** 通过 instanceof 类型守卫 进一步确认类型 */
cls.wangwang();
} else if (cls instanceof Cat) {
/** 同上 */
cls.miaomiao();
}
}

Playground Link

is @scope lexical

利用函数返回值确认类型

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
enum CUSTOM_ENUM {
A = 'a',
B = 'b',
}

interface Base {
type: CUSTOM_ENUM;
}

interface A extends Base {
type: CUSTOM_ENUM.A;
a?: number;
}

interface B extends Base {
type: CUSTOM_ENUM.B;
b?: number;
}

function isA(val: Base): val is A {
return val?.type === CUSTOM_ENUM.A;
}

/** 假设你不知道是啥类型 */
const a: any = {
type: CUSTOM_ENUM.A,
a: 1,
};
const b: any = 1;


console.log(a.a); // 恰好没有出错
if (isA(a)) {
console.log(a.a) // good
}

console.log(b.a); // 潜在的运行时错误
if (isA(b)) {
console.log(b.a) // 避免了出错
}

Playground Link

as @scope lexical

类型断言

1
2
3
4
5
6
7
8
function double(s: number) {
return Number(s) * 2;
}

const a: unknown = '1';

console.log(double(a as number));

Playground Link

in @scope runtime

es标准中的 in 标识符

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
interface B {
b: string;
}
interface A {
a: string;
}


function foo(x: A | B) {
// good
if ('a' in x) {
return x.a;
}
return x.b;
}

const a: A = {
a: 'a',
}

const b: B = {
b: 'b',
}

console.log(foo(a));

Playground Link

! @scope runtime

可以绕过严格空判断,TypeScript 会认为该属性一定存在

1
2
3
4
5
6
7
8
9
10
11
12
interface A {
s?: {
t: string;
};
}
const a: A = {
s: {
t: 'something',
},
};
console.log(a.s.t); // error,s 可能为空
console.log(a.s!.t); // 不会报错,s 被认为一定存在

Playground Link


类型索引

获取 interface / type 中的某个属性

T[K] @scope lexical

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface PersonStruct {
name: string;
age: number;
}

class Person implements PersonStruct {
// 通过 T[K] 的模式设置属性类型
name: PersonStruct['name'];
age: PersonStruct['age'];

constructor({ name, age }: PersonStruct) {
this.name = name;
this.age = age;
}
}

Playground Link


类型属性枚举

列举 interface 中的子属性

keyof @scope lexical

枚举 interface 中的成员

1
2
3
4
5
6
interface PersonStruct {
name: string;
age: number;
}

type PersonKeys = keyof PersonStruct; // name | age

Playground Link


属性赋值

把枚举的到的属性赋值到目标上

in @scope lexical

这个 in 与类型守卫中的 in 的含义是不一样的
静态分析中的 in 是赋值

1
2
3
4
5
6
7
8
interface PersonStruct {
name: string;
age: number;
}

type Mapper = {
[k in keyof PersonStruct]: string; // { name: string; age: string; }
}

Playground Link


属性状态

描述一个属性的状态

readonly @scope lexical

被标记的属性无法被修改/赋值

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
interface PersonStruct {
readonly name: string;
readonly age: number;
}

const person: PersonStruct = {
name: 'xxx',
age: 19,
}

person.age = 1; // error
person.name = 'yyy'; // error

class Person {
readonly name: string;
readonly age: number;

constructor({ name, age }: PersonStruct) {
this.name = name;
this.age = age;
}
}

const p = new Person({ name: 'xxx', age: 198 });
p.age = 1; // error
p.name = 'yyy'; // error

Playground Link

? @scope lexical

属性状态-可选属性

1
2
3
4
5
6
7
8
9
interface PersonStruct {
name: string;
age?: number;
}

// good, age可以有也可以没有
const person: PersonStruct = {
name: 'xxx',
}

Playground Link

- @scope lexical

移除属性状态的标记

1
2
3
4
5
6
7
8
interface PersonStruct {
readonly name: string;
age?: number;
}

type Remove = {
-readonly [k in keyof PersonStruct]-?: PersonStruct[k]; // { name: string; age: number }
}

Playground Link


类型推导

提供一个类型(不需要具体实现),通过推导的方式得到其类型或其子属性类型

infer @scope lexical

推导出某个类型中的某个属性类型

1
2
3
4
5
6
7
8
interface PersonStruct {
name?: string;
age: number;
}

type PersonList = PersonStruct[];

type ListItemType = PersonList extends (infer T)[] ? T : unknown; // PersonStruct

Playground Link


优雅的处理全局变量

优雅解决window下挂载的自定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** typings/window.d.ts */
declare module _global {
export const someVar: number;
}

interface Window {
_global: typeof _global;
}


/** src/you path/somefile.d.ts */
console.log(window._global.someVar); // good

console.log(_global.someVar); // good

Playground Link


优雅的处理第三方库

1
2
3
4
5
6
7
8
9
10
// 有些第三方库可能没有 d.ts

/** typings/react.d.ts */
declare namespace SomeThirdModule {
export const someFunc: () => void;
}

/** src/you path/somefile.d.tsx */

SomeThirdModule.someFunc(); // good

Playground Link


优雅的处理非模块文件

图片和样式文件会提示非 module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** typings/ext.d.ts */

declare module '*.scss' {
const value: { [className: string]: string };
export default value;
}

/**
* @description svg 模块
*/
declare module '*.svg' {
import * as React from 'react';

const src: React.SVGProps<SVGSVGElement>;
export default src;
}

Playground Link


TypeScript 内置类型函数 列举(不完全)

Pick

Record

Partial

Required

Readonly

Exclude

Extract

Omit

NonNullable

Parameters

ConstructorParameters

ReturnType

InstanceType

ThisType