- Published on
自用 TS 手册
- Authors
- Name
- Eric Yuan
- @EricYuansz
只会写基础TS就跟处处写重复代码不会抽通用模块、功能函数的 js 初学者一样一样的,run是能run,就是有点费键盘...
- TS 官方文档, 基础部分建议通读,本文将记录一些有用且常用的 TS 概念和技巧
基础类型
权重:越往上越大,越往下越小。
基础运算:
|
,联合类型- 存在父子关系,则结果是类型权重较
大
的那个;没有父子关系则为联合类型
- 在二进制位运算里,
|
会有增加属性的功能,TS 里也类似,使得类型范围增加,但赋值时只能命中其中某一个类型
- 存在父子关系,则结果是类型权重较
&
,交叉类型- 存在父子关系,则结果是类型权重较
小
的那个;没有父子关系则为never
- 在对象类型中就是合并对象类型
- 存在父子关系,则结果是类型权重较
特殊的 any
既为顶部类型也为底部类型:
type t1 = any | unknown // any
type t2 = any & unknown // any
特殊的,never
在联合类型中会被过滤掉:
// 获取函数属性的键值
interface Demo {
a: string
b: number
d: () => string[]
e: () => string[]
f: () => number[]
}
type GetDemoFnKeyTypes<T, K> = {
[P in keyof T]: T[P] extends K ? P : never // TS 中的遍历属性,类似与 JS 的 forin 语句
}[keyof T]
type FnKeyTypes = GetDemoFnKeyTypes<Demo, () => string[]> // 'd' | 'e'
取属性类型
JS 对象通过 .
或[prop]
获取属性值,TS 中则可以通过后者 [prop]
获取属性类型:
interface Person {
name: string
age: number
}
type Name = Person['name'] // string
/** 数组、元组、字符穿也可以通过索引获取对应位置的类型*/
特殊的,基础类型可以取到原型的定义:
// String.prototype.concat 的类型定义
type Concat = 'hello'['concat'] // (...strings: string[]) => string
// Number.prototype.toFixed
type ToFixed = 1['toFixed'] // (fractionDigits?: number | undefined) => string
keyof & typeof
keyof
获取公有属性的联合类型;typeof
推导出 js 变量的对应类型:
class Person {
public name: string
public age: number
private gender: boolean
}
type T1 = keyof Person // 'name' | 'age'
type T2 = keyof any // string | number | symbol
const Demo = [1, 'hello', true]
type T3 = (typeof Demo)[number] // number | string | boolean
// as const 可以让 typeof 在获取类型的时候获取到字面量类型
const Demo = [1, 'hello', true] as const
type T4 = (typeof Demo)[number] // 1 | 'hello' | true
// 这在用 Record 构造对象时比较常见
// 比如要临时构造一个对象,key 取自数组 A,value 需要从接口返回的数组中获取那么可以这么做
const A = ['a', 'b', 'c'] as const
const temp = {} as Record<(typeof A)[number], any> // {a: xxx, b: xxx, c : xxx}
实际上,一般 keyof 和 typeof 一起配合使用的场景非常多,多加练习即可。
interface & type
TS 入门时多多少少都会对这两玩意儿有点困惑吧👻
interface
- 自身只能表示
object/class/function
的类型 - 同名的
interface
会自动聚合
- 自身只能表示
type
- 在 TS 中充当了
别名
的作用,几乎可以表示一切类型 - 不能同名,但支持更复杂的类型操作
- 在 TS 中充当了
鉴于两者的特点:建议开发库时对外暴露的类型尽量使用 interface/class
,方便使用者自行扩展,其他时候尽量使用 type
即可。
逆变 & 协变
协变(Covariance)和逆变(Contravariance)的概念用于描述类型系统中子类型和父类型之间的关系。
简单讲:
协变
,意味着子类型可以赋值给父类型逆变
,意味着父类型可以赋值给子类型
在 TS 中特殊的,函数的参数是逆变的,函数的返回值是协变的
type Animal = {
name: string
}
type Dog = Animal & { age: number }
let a!: () => Animal
let b!: () => Dog
a = b // ok
b = a // error
let c!: (param: Animal) => void
let d!: (param: Dog) => void
c = d // error
d = c // ok
TS 为了方便,把函数参数默认设置为了双变的,所以默认情况下,上面的 c = d 也是 ok 的。为了让类型系统更加准确,可以在 tsconfig 中配置
"strictFunctionTypes": true
,这样函数参数只有逆变的特性了。
泛型 & infer
泛型需要注意的一个点是在进行约束时,如果泛型是联合类型,那么满足分配律
:
type P<T> = T extends 'X' ? 1 : 2
type T1 = P<'X' | 'Y'> // 1 | 2
type T2 = 'X' | 'Y' extends 'X' ? 1 : 2; // 2 直接进行约束则没有分配律
infer
用在 extends
之后,用来推断类型,是类型体操的关键字段之一:
type A<T> = T extends {a: infer U, b: infer U} ? U : any;
type B = A<{a: string, b: number}> // string | number
内置类型
熟练使用内置类型,会让体操事半功倍,比如我之前用 zustand 时,封装了一个 loading 函数:
/**
* 用到了 Parameters & ReturnType
* 使用的时候 const res = withLoading(apiService)(params),
* 得益于 ts 类型提示,上面的 apiService 的参数也提示到 params 的位置
*/
export const withLoading = <T extends (...args: any[]) => Promise<any>>(asyncFunction: T) => {
const setLoading = useLoading.getState().setLoading
return async (...args: Parameters<T>): Promise<ReturnType<T> | undefined> => {
setLoading(true)
const [err, res] = await till(asyncFunction(...args))
setLoading(false)
if (err) {
console.error(err)
return
}
return res
}
}
最后,总而言之,言而总之 --- 菜就多练🤣
如果你有 TS 使用的小技巧,欢迎在下面的评论区留言👏🏻