一、常用技巧
1. 函数重载
export function util(str: string): string
export function util(str: number): number
export function util(str: string | number): number | string {
return str;
}
const a: string = util('a');
const b: number = util(1);
2. 捕获变量类型
通过 typeof
关键字捕获变量的类型
let a = 123;
let b = { x: 0, y: 1 };
type A = typeof a; // number 类型
type B = typeof b; // { x: number, y: number }
3 . 函数绑定 this
上下文
通过绑定函数的第一个参数 this
指定当前函数的上下文(官方文档)
const obj = {
say(name: string) {
console.log('Hello,', name);
}
}
// 通过第一个参数 this 绑定当前函数上下文
function foo(this: typeof obj, str: string) {
this.say(str);
}
4. 使用 Never
Never 表示永远不会发生的类型(官方文档),比如抛错函数的返回、死循环函数的返回
// 抛出异常
function unexpected(): never {
throw Error('Unexpected');
}
function test(x: boolean): number {
if (x) {
return 1;
}
unexpected(); // 因为 never 表示永远不会发生的类型,所以这里不会要求 number 作为返回值
}
// 官方文档给的死循环的例子
function infiniteLoop(): never {
while (true) {}
}
5. 索引签名
interface A {
x: number
}
const a: A = {x: 1}
// 接口键类型为: string, 值类型为: boolean
interface B {
[key: string]: boolean
}
const b: B = {
a: true,
b: false
}
// in 表示遍历,键名称包括: 'a', 'b', 'c',值类型为: string
type C = {
[key in 'a' | 'b' | 'c']: string
}
const c: C = {
a: '1',
b: '2',
c: '3'
}
6. 通过索引访问类型
(官方文档)
interface A {
a: string
b: {
c: boolean
}
}
type Aa = A['a'] // string 类型
type Abc = A['b']['c'] // boolean 类型
// 提取元祖对应的类型
type B = [string, number]
type B0 = B[0] // string 类型
type B1 = B[1] // number 类型
// 提取数组的元素类型
type C = Array<string | number | boolean>
// 通过访问数组类型的任意下标获取数组元素类型
type D = C[0] // 类型 (string | number | boolean)
7. 使用 as
可以通过 as
关键字指定值的类型,可以很轻松的处理掉一些有歧义的场景
function foo(str?: string) {
return str.toString();
}
const obj = {
toString() {
}
}
foo(obj as string)
8. 条件类型
顾名思义条件类型就是根据条件决定使用哪种类型(官方文档),条件类型大概长这样:T extends U ? X : Y
// 与 JS 的三元表达式很类似,如果 T 的类型是 string, 则返回 string 类型,否则,返回 number 类型
type F<T> = T extends string ? string : number
const a: F<string> = 'abc'; // 泛型的类型是 string, 则为 string 类型
const b: F<number> = 1; // 泛型的类型是非 string, 则为 number 类型
const c: F<boolean> = 2; // 泛型的类型是非 string, 则为 number 类型
9. 使用 is
关键字 is
, 用于类型保护,我的理解就是更高级的 boolean
// 返回类型 boolean,而且告诉编译器 value 类型是 string 时,返回 true
function isString(value: any): value is string {
return typeof value === 'string';
}
function fn(val: number | string) {
if (isString(val)) { // 如果这里判断通过,表示 val 类型是 string
val.trim();
} else { // 否则,val 类型为 number
val.toFixed(2);
}
}
官方的 Array.isArray 方法定义就使用了 is
关键字:
interface ArrayConstructor {
isArray: (arg: any) => arg is any[];
}
10. 通过修饰构造函数参数定义类属性
通过修饰构造函数的参数可以快捷定义类属性并自动赋值,看下面例子:
class Point {
constructor(private x: number, private y: number) {
}
}
class Point {
private x: number
private y: number
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
以上2种写法是等同的,但是上面的可以少写一些代码。除了用 private
修饰构造函数的参数,其他类似的修饰关键字还有: public
、protected
、readonly
, 详见官方文档
二、泛型
泛型可以提供更灵活的类型约束,而不局限于某种类型。常用于 function
、class
、interface
、type
(官方文档)
1. 使用示例
function
function foo<T>(arg: T): T {
return arg;
}
class
class Foo<T> {
name: T
constructor(name: T) {
this.name = name;
}
getName(): T {
return this.name
}
}
const foo = new Foo<number>(3);
const name: number = foo.getName();
interface
interface Foo<T> {
a: T
}
const foo: Foo<number> = { a: 1 }
type
type Foo<T> = { a: T }
const foo: Foo<boolean> = { a: false }
2. 泛型约束
可以通过 extends
关键字约束泛型
// 指定泛型的类型限制于: string | number, 其他类型将会报错
interface A<T extends string | number> {
[key: string]: T
}
const a: A<string> = {a: 'abc'}
const b: A<number> = {a: 123}
3. 指定泛型默认类型
interface B<T = number> {
[key: string]: T
}
const c: B = { a: 123 }
const d: B<string> = { a: 'abc' }
三、TypeScript 中的一些运算符
下面梳理了 TypeScript 中的一些运算符,对理解后面的内容非常有帮助
?
: 表示可选的参数或属性,可以用于函数参数、接口的属性等-?
: 取消可选标识(扩展:类似的还有-readonly
表示取消 readonly 标识)in
: 表示遍历类型typeof
: 捕获变量类型,上面有例子keyof
: 将对象类型生成其键的字符串或数字的集合,常与in
结合使用infer
: 表示推断类型,下面讲 TS 内置类型Parameters
实现的时候会有具体例子
四、实现 TypeScript 内置类型
TypeScript 提供了一些常用的类型,开发者可以直接使用它们,下面看看这些类型是如何实现的(内置类型使用文档)
1. Record
构造一个键为 K, 值为 T 的键值对类型
// keyof any 包含: string | number | symbol
type Record<K extends keyof any, T> = {
// 表示键类型是约束于 K (string | number | symbol) 中的一种或多种,值类型为 T
// in 表示遍历
[P in K]: T
}
const foo: Record<string, boolean> = {
a: true
}
const bar: Record<'x' | 'y', number> = {
x: 1,
y: 2
}
2. Partial
使 T 中的所有属性都变成可选的
type Partial<T> = {
// 将原始类型 T 的所有属性加上 ? 修饰,变成可选的
[P in keyof T]?: T[P]
}
interface Foo {
a: string
b: number
}
const foo: Partial<Foo> = {
b: 2 // `a` 已经不是必须的了
}
3. Required
与 Partial 相反,将 T 中的所有属性变成必须的
type Required<T> = {
// 将原始类型 T 的所有属性加上 -? 修饰,变成必须的
// -? 表示移出 ? 这个标识
[P in keyof T]-?: T[P]
}
interface Foo {
a: string
b?: number
}
const foo: Required<Foo> = {
a: 'abc',
b: 2 // `b` 已经变成必选的了
}
4. Readonly
使 T 中的所有属性变成只读
type Readonly<T> = {
// 将原始类型 T 的所有属性加上 readonly 修饰,属性变成只读的
readonly [P in keyof T]: T[P]
}
interface Foo {
a: string
}
const foo: Readonly<Foo> = {
a: 'abc'
}
// foo.a = 'def' // 只读,不可修改
// 此外,可以使用 -readonly 修饰,表示去掉 readonly 修饰
type Writable<T> = {
-readonly [P in keyof T]: T[P]
}
interface Bar {
readonly a: string
}
const bar: Writable<Bar> = {
a: 'abc'
}
bar.a = 'def'; // `Bar['a'] 已经去掉了 readonly 修饰,可以修改了
5. Pick
从类型 T 中选择一些属性,这些属性来自于集合 K
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
interface Foo {
a: string
b: number
c: boolean
}
const foo: Pick<Foo, 'b' | 'c'> = {
b: 1,
c: false
}
6. Exclude
排除掉 T 中能赋值给 U 的类型
// 如果 T 是 U 的子类型,则返回 never, 否则返回 T
type Exclude<T, U> = T extends U ? never : T
// 'a' | 'b' | 'c' 中排除掉 'b', 只能为 'a' 或 'c'
let foo: Exclude<'a' | 'b' | 'c', 'b'> = 'a'
foo = 'c'
7. Extract
与 Exclude 相反,提取 T 中能赋值给 U 的类型
// 如果 T 是 U 的子类型,则返回 T, 否则返回 never
type Extract<T, U> = T extends U ? T : never
// 'a' | 'b' | 'c' 中提取 'b', 只能为 'b'
let foo: Extract<'a' | 'b' | 'c', 'b'> = 'b'
8. Omit
省略掉 T 中的一些属性,这些属性来自于集合 K
// 1. 从 T 的属性中排除掉 K,得到剩下的一个属性集合
// 2. 从 T 中选择剩下的集合
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
interface Foo {
a: string
b: number
c: boolean
}
// 从 Foo 中省略 'c', 接口 Foo 只剩下 'a' 和 'b'
let foo: Omit<Foo, 'c'> = {
a: 'a',
b: 1
}
9. NonNullable
排除掉 null 、undefined 类型
// 排除掉 null | undefined
type NonNullable<T> = T extends null | undefined ? never : T
type Foo = string | null
const a: NonNullable<Foo> = 'a' // 不能赋值给 null 或 undefined
10. Parameters
根据函数的参数,返回对应的元组类型
// 主要说一下 infer P, 在这里表示待推断的函数参数
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
type Foo = (a: string, b: number) => void
const a: Parameters<Foo> = ['a', 1] // 元组 [string, number]
11. ConstructorParameters
根据构造函数的参数,返回对应的元组类型
// 跟 Parameters 类似
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never
interface Foo {
new(a: string, b: number)
}
const a: ConstructorParameters<Foo> = ['a', 1] // 元组 [string, number]
12. ReturnType
返回函数的返回类型
// 跟 Parameters 类似
type ReturnType<T extends (...args: any) => any> = T extends (...args:any) => infer R ? R : any
type Foo = () => boolean
const a: ReturnType<Foo> = true // 返回 boolean 类型
13. InstanceType
// 跟 Parameters 类似
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any