# 笔记
# 2022 年 5 月 1 日
# typeof
在 typescript
中,typeof
可以用来获取一个变量声明或对象的类型
interface IPerson {
name: string
age: number
}
const someone: IPerson = { name: "张三", age: 21 }
type IMan = typeof someone // -> IPerson
也可以用来获取函数对象的类型
function getString(str: number): string {
return str + ""
}
type Func = typeof getString // -> (str: number) => string
# 2022 年 5 月 2 日
# keyof
keyof
操作符可以捕获一个类型的键,如捕获变量的键的名称,用法如下:
const colors = {
red: "red",
blue: "blue"
}
// 首先通过 typeof 操作符获取 colors 变量的类型,然后通过 keyof 操作符获取该类型的所有键:'red' | 'blue'
type Colors = keyof typeof colors
let color: Colors
color = "red" // Ok
color = "blue" // Ok
color = "anythingElse" // Error
# 2022 年 5 月 3 日
# 泛型和泛型约束案例之一
function prop(obj: object, key: string) {
return obj[key]
}
在上面代码中,为了避免调用 prop
函数时传入错误的参数类型,我们为 obj
和 key
参数设置了类型,分别为 {}
和 string
类型。但实际上并没有那么简单,以上代码经过 ts
编译后会报以下错误
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'
元素隐式地拥有 any
类型,因为 string
类型不能被用于索引 {}
类型。要解决这个问题,你可以使用以下非常暴力的方案:
function prop(obj: object, key: string) {
return (obj as any)[key]
}
很明显该方案并不是一个好的方案,我们来回顾一下 prop
函数的作用,该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用 keyof
操作符:
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
首先定义了 T
类型并使用 extends
关键字约束该类型必须是 object
类型的子类型,然后使用 keyof
操作符获取 T
类型的所有键,其返回类型是联合类型,最后利用 extends
关键字约束 K
类型必须为 keyof T
联合类型的子类型。
# 2022 年 5 月 4 日
# 内置类型别名之 Partial && Required
// Make all properties in T optional
type Partial<T> = {
[P in keyof T]?: T[P]
}
// Make all properties in T required
type Required<T> = {
[P in keyof T]-?: T[P]
}
如上代码所示,Partial<T>
的作用是将某个类型中的所有属性都变成可选属性;而 Required<T>
的作用则是将某个类型中的可选属性都变成必选属性,其中 -?
表示移除可选项 ?
示例:
export interface IResult {
code?: number
message: string
data?: string
}
type IResultPartial = Partial<IResult>
type IResultRequired = Required<IResult>
// 结果
// type IResultPartial = {
// code?: number | undefined;
// message?: string | undefined;
// data?: string | undefined;
// }
// type IResultRequired = {
// code: number;
// message: string;
// data: string;
// }
# 2022 年 5 月 5 日
# 内置类型别名之 Exclude && Extract
// Exclude from T those types that are assignable to U
type Exclude<T, U> = T extends U ? never : T
// Extract from T those types that are assignable to U
type Extract<T, U> = T extends U ? T : never
如上代码所示,Exclude<T, U>
的作用是将 T
类型中属于 U
类型的属性移除掉;而 Extract<T, U>
则是从 T
中提取出 U
注意以上 extends
的作用为类型约束,当 T
是 U
的子类型时,T extends U
返回的是 true
,否则返回 false
示例:
interface T1 {
name: string
age: number
email: string
phone: number
}
interface T2 {
email: string
phone: number
}
type T3 = string | number | number[]
type T4 = string
// 需要区分联合类型与接口的区别
type T5 = Exclude<keyof T1, keyof T2> // -> type T5 = "name" | "age"
type T6 = Exclude<T3, T4> // -> type T6 = number | number[]
type T7 = Extract<keyof T1, keyof T2> // -> type T7 = "email" | "phone"
type T8 = Extract<T3, T4> // -> type T8 = string
# 2022 年 5 月 6 日
# 内置类型别名之 Pick && Omit
// From T, pick a set of properties whose keys are in the union K
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
// Construct a type with the properties of T except for those in type K.
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
如上代码所示,Pick<T, K extends keyof T>
的作用是将 T
中的子类型 K
提取出来;Omit<T, K extends keyof any>
的作用则是将 T 中除 K 类型以外的所有属性
示例:
interface T1 {
name: string
age: number
email: string
phone: number
}
interface T2 {
email: string
phone: number
}
type T3 = "email" | "phone"
type T5 = Pick<T1, keyof T2> // -> type T5 = { email: string; phone: number; }
type T6 = Pick<T1, T3> // -> type T6 = { email: string; phone: number; }
type T7 = Omit<T1, keyof T2> // -> type T7 = { name: string; age: number; }
type T8 = Omit<T1, T3> // -> type T8 = { name: string; age: number; }
# 2022 年 5 月 7 日
# TS 中的 extends 关键字
# 2022 年 5 月 8 日
# const 断言
# 2022 年 5 月 9 日
# 协变与逆变
# 2022 年 5 月 10 日
# readonly & const
参考文档:
- https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-properties (opens new window)
- https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-tuple-types (opens new window)
# 2022 年 5 月 11 日
# 泛型、泛型接口、泛型类、非泛型函数签名?
# 2022 年 5 月 12 日
# ts 中的 infer 关键字
infer
关键字是在 2.8
版本中新增的,在有条件类型的 extends
子语句中,允许出现 infer
声明,它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的 true
分支中被引用。 允许出现多个同类型变量的 infer
。
如下代码会提取函数类型的返回值类型:
// 例1:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
// infer R 为 待推断的返回类型
// 例2:
type ParamType<T> = T extends (param: infer R) => any ? R : T
大白话理解即为:如果例 1 中的 T
能赋值于 (...args: any[]) => infer R
,那么 infer R
就是 T
的返回类型;如果例 2 中的 T
能赋值于 (param: infer R) => any
那么 infer R
就是参数 param
的类型
加深理解:
type Unpacked<T> = T extends (infer U)[] // 指待推断的数组类型
? U
: T extends (...args: any[]) => infer U // 指待推断的函数返回值类型
? U
: T extends Promise<infer U> // 指待推断的参数类型
? U
: T
type T0 = Unpacked<string> // string
type T1 = Unpacked<string[]> // string
type T2 = Unpacked<() => string> // string
type T3 = Unpacked<Promise<string>> // string
type T4 = Unpacked<Promise<string>[]> // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>> // string
参考文档:
# 2022 年 5 月 13 日
# 内置类型别名之:ReturnType
// Obtain the return type of a function type
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
如上代码所示,ReturnType<T>
主要用于获取函数返回值类型
更多示例:
function f1(s: string) {
return { a: 1, b: s }
}
type T1 = ReturnType<() => string> // string
type T2 = ReturnType<(s: string) => void> // void
type T3 = ReturnType<<T>() => T> // unknown ? {}
type T4 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T5 = ReturnType<typeof f1> // { a: number, b: string }
type T6 = ReturnType<any> // any
type T7 = ReturnType<never> // never?any
type T8 = ReturnType<string> // Error
type T9 = ReturnType<Function> // Error
# 2022 年 5 月 24 日
# Object.keys 遍历赋值
如下栗子,在 js
中我们经常用 Object.keys
获取一个对象的属性进行遍历赋值,但是在 ts 中使用会提示异常
type IObj = {
a: number
b: string
c: number[]
}
const obj: IObj = {
a: 1,
b: "2",
c: [1, 2, 3]
}
const data = {
a: 10,
b: "string",
c: [1, 2, 3, 4, 5, 6, 7],
d: true
}
Object.keys(obj).forEach(key => {
// 报错
obj[key] = data[key]
})
// 异常信息 ->
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IObj'.
// No index signature with a parameter of type 'string' was found on type 'IObj'.
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: string; c: number[]; d: boolean; }'.
// No index signature with a parameter of type 'string' was found on type '{ a: number; b: string; c: number[]; d: boolean; }'.
在 Object.keys
获取到的属性 list
其实是 string[]
,而非 (keyof IObj)[]
,因此需要再使用类型断言
Object.keys(obj).forEach(key => {
obj[key as keyof IObj] = data[key]
})
// 可能在playground仍然会报下面这个异常,这时只需要给obj加上 as any 断言即可
// Type 'any' is not assignable to type 'never'
Object.keys(obj).forEach(key => {
;(obj as any)[key as keyof IObj] = data[key]
})
写成方法则如下:
function traversalAssign<T extends object, Q extends object>(obj1: T, obj2: Q) {
Object.keys(obj1).forEach(key => {
obj1[key as keyof T] = (obj2 as any)[key]
})
}
// 或
function traversalAssign<T, Q extends object>(obj1: T, obj2: Q) {
let item: keyof T
for (item in obj1) {
obj1[item] = (obj2 as any)[item]
}
}