预备动作
阅读本篇之前你可能需要一定的TypeScript基础,如果还没开始上手建议从这里开始
以下是传送门
- TypeScript Document 官方文档
- TypeScript HandBook 小手册
- TypeScript Playground 旧版练习场
- 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);
BaseFunc(s2);
|
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) { cls.wangwang(); cls.miaomiao(); if (cls instanceof Dog) { 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) }
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) { 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); console.log(a.s!.t);
|
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 { 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;
|
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; }
|
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; person.name = 'yyy';
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; p.name = 'yyy';
|
Playground Link
?
@scope lexical
属性状态-可选属性
1 2 3 4 5 6 7 8 9
| interface PersonStruct { name: string; age?: number; }
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]; }
|
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;
|
Playground Link
优雅的处理全局变量
优雅解决window下挂载的自定义变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| declare module _global { export const someVar: number; }
interface Window { _global: typeof _global; }
console.log(window._global.someVar);
console.log(_global.someVar);
|
Playground Link
优雅的处理第三方库
1 2 3 4 5 6 7 8 9 10
|
declare namespace SomeThirdModule { export const someFunc: () => void; }
SomeThirdModule.someFunc();
|
Playground Link
优雅的处理非模块文件
图片和样式文件会提示非 module
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
declare module '*.scss' { const value: { [className: string]: string }; export default value; }
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
Omit
NonNullable
Parameters
ConstructorParameters
ReturnType
InstanceType
ThisType