交叉类型
交叉类型使用&
- 父类型收敛
type A0 = 1 & number; // 1
type A1 = '1' & string; // '1'
type A2 = true & boolean; // true
type A3 = any & 1; // any;
type A4 = any & boolean; // any
type A5 = any & never; // never
interface X {
c: string
d: string
}
interface Y {
c: number
d: string
}
type XY = X & Y; // 此时c类型未never
如果对象相同的属性是字面量类型或字面量类型组合的联合类型,那么运算结果将是never
type Foo = {
age: number
name: string
}
type Bar = {
age: number
name: boolean // 可以看作是true|false
}
type Baz = Foo & Bar; // never
- 应用
- 实现一个PartialByKeys工具类型,用于把对象中指定的key变成可选的
type PartialByKeys<T, K extends keyof T> = {
[P in K]?: T[P];
} & Pick<T, Exclude<keyof T, K>>
type S1 = {
name: string
age: number
address: string
}
type R1 = PartialByKeys<S1, 'address'>
const r1: R1 = {
name: 'Mark',
age: 12
}
- 实现一个RequiredByKeys工具类型,用于把对象中指定的key变成必选的
type RequiredByKeys<T, K extends keyof T> = {
[P in K]: T[P];
} & Partial<Pick<T, Exclude<keyof T, K>>>
type S1 = {
name: string
age: number
address: string
}
type R1 = RequiredByKeys<S1, 'address'>
const r1: R1 = {
address: '1'
}
索引签名
- 用法
{ [key: KeyType]: ValueTYpe }
// KeyType: string 、number、symbol、模板字面量
type T0 = {
[key: number]: string
}
type T1 = keyof T0 // number
type T10 = {
[key: string]: string
}
type T11 = keyof T10 // number | number, 当用作属性访问器中的键时,js会隐式的把数字转为字符串类型
// 模板字面量类型
type T20 = {
[key: `${string}Changed`]: () => void
}
const o20: T20 = {
'valueChanged': () => {}
}
- 和内置类型
Record的区别 索引类型键的类型:string 、number、symbol、模板字面量
Record键的类型:可以是字面量类型或字面量类型的联合类型
模板字面量类型
type Direction = 'left' | 'right' | 'top' | 'bottom'
type CssPadding = `padding-${Direction}` // "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
type MarginPadding = `margin-${Direction}` // "margin-left" | "margin-right" | "margin-top" | "margin-bottom"
type GetterName<T extends string> = `get${Capitalize<T>}`
type U1 = GetterName<'foo'> // 'getFoo'
type Direction = 'left' | 'right' | 'top' | 'bottom'
type InferRoot<T> = T extends `${infer R}${Capitalize<Direction>}` ? R : T
type U2 = InferRoot<'marginLeft'> // margin
type Getters<T> = {
// string & K 过滤非string类型的键
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
type User = {
id: number
name: string
}
type U3 = Getters<User>
协变和逆变
- 协变 将子类型的变量赋值给父类型的变量就是协变
interface Animal {
name: string
}
interface Cat {
name: string
miao(): void
}
const cat1: Cat = {
name: '悠米',
miao: () => {
console.log('喵喵')
}
}
const animal: Animal = cat1
console.log(animal.name)
- 逆变
interface Animal {
name: string
}
interface Cat {
name: string
miao(): void
}
let fun1: (x: Animal) => void = (x: Animal)=>{}
let fun2: (x: Cat) => void = (x: Cat)=>{}
fun1 = fun2 // error
fun2 = fun1 // ok
我们发现:参数逆变是可以的,协变却发生错误。为什么报错呢,因为这样会造成类型不安全的。
我们可以在tsconfig.json中设置"strictFunctionTypes": false,使之不报错,加上如下代码:
const cat1: Cat = {
name: '悠米',
miao: () => {
console.log('喵喵')
}
}
let animal: Animal = {
name: '小A'
}
fun1(animal) // 运行时报错了
一些关键字
is
vue3源码中有这样一个函数
const isString = (val: any): val is string => typeof val === 'string'
所以需要使用is特性. ts可以根据 if 判断推断出当前的val为string类型
如果没有没有is
function isString(s: unknown): boolean {
return typeof s === 'string'
}
function toUpperCase(x: unknown) {
if(isString(x)) {
x.toUpperCase() // Error, Object is of type 'unknown'
}
}
keyof
type KEY = keyof any //即 string | number | symbol
type KEY2 = keyof unknown // 即 never
export function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
typeof
enum HttpMethod {
Get,
Post
}
type Method = keyof typeof HttpMethod // "Get" | "Post"
extends
type R1 = 1 extends 1 ? true : false // true
type R2 = 1 extends 2 ? true : false // false
type R3 = 'abc' extends 'a' ? true : false // false
type R4 = { a: true } extends { a: true } ? true : false // true
type R5 = { a: true; b: false } extends { a: true } ? true : false // true
type R6 = { a: true } extends { a: true; b: false } ? true : false // false
type R7 = 'a' | 'b' | 'c' extends 'a' ? true : false // false
在泛型中使用呢
TypeScript: Documentation - Conditional Types (typescriptlang.org)
根据文档可知即当条件类型作用于泛型类型时,联合类型会被拆分使用。
- 分布式条件类型:裸类型参数:没有被
T[]、[T]、Promise<T>包裹
type MyExclude<T, U> = T extends U ? never : T
type R10 = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
为什么R10是'b' | 'c',因为
MyExclude<'a' | 'b' | 'c', 'a'>会被拆成'a' extends 'a'、'b' extends 'a'、'c' extends 'a'
infer
// 仅条件类型的"extends"子句中才允许"infer"什么声明
type UnpakcedArray<T> = T extends (infer U)[] ? U : T
type S1 = UnpakcedArray<number[]> // number
Unpakced
type Unpakced<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T
type S1 = Unpakced<number[]> // number
type S2 = Unpakced<string[]> // string
type S3 = Unpakced<() => boolean> // boolean
type S4 = Unpakced<Promise<string>> // string
type S5 = Unpakced<Promise<string>[]> // Promise<string>
type ProperType<T> = T extends { id: infer U; name: infer R } ? [U, R] : T;
type User = {
id: number;
name: string;
age: number;
}
type U1 = ProperType<User> // [number, string]
在协变位置上,若同一个类型变量存在多个候选者,则最终类型被推断为联合类型
type ProperType<T> = T extends { id: infer U; name: infer U } ? U : T;
type User = {
id: number;
name: string;
age: number;
}
type U1 = ProperType<User> // string | number
在逆变位置上,若同一个类型变量存在多个候选者,则最终类型被推断为交叉类型
type Foo<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never
type U1 = Foo<{ a: (x: string) => void, b: (x: number) => void }> // number & string => never
- 4.7新特性 可以给infer type添加可选的extends子句
type FirstIfString<T> = T extends [infer S extends string, ...unknown[]] ? S : never
declare
- 生命全局变量、全局函数、全局类、全局枚举的。
- 注意,声明全局函数不用实现
- declare module 语法,扩展已有类型
比如给组件实例增加$axios属性
main.tsimport { AxiosInstance } from 'axios' declare module "@vue/runtime-core" { interface ComponentCustomProperties { $axios: AxiosInstance } }import axios from 'axios' app.config.globalPropertie.$axios = axios
面试题
interface 和 type的区别
- type可以做到,但interface不能做到的事情
- 可以定义其他类型别名,如
type MyBoolean = boolean - 可以使用
typeof操作符来定义,如type myType = typeof obj1 - 联合类型,如
type unionType = type1 | type2 - 元组类型,如
type tupleType = [type1, type2]
- 可以定义其他类型别名,如
- interface可以做到,而type不能做到事
- 声明合并
interface test1 { name: string } interface test1 { age: number } // 跟下面的等价 // interface test1 { // name: string // age: number // } - 如果是type的话,会报重复定义的错误
- 声明合并
any和unknown 的区别
- any: 我不在乎它的类型,然后可以进行任何操作
- unknown: 我不知道它的类型,所以必须进行类型检查或类型断言后才能进行操作。
- 任何类型都可以赋给any或unknown类型
- unknown类型的变量只能赋值给any和unknown类型
type T0 = keyof any; // string | number | symbol
type T1 = keyof unknown; // never
type T10<T> = {
[P in keyof T]: number
}
type T11 = T10<any>; // { [x: string]: number }
type T12 = T10<unknown>; // {}
内置的类型
Partial<T>
将类型
T的所有属性标记为可选属性
type Partial<T> = {
[P in keyof T]?: T[P];
}
Required<T>
将类型
T的所有属性标记为必选属性
type Required<T> = {
[P in keyof T]-?: T[P];
}
Readonly<T>
将类型
T的所有属性标记为readonly, 即只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
Pick<T>
从类型
T中过滤出某个或某些属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
Record<K, T>
标记 K 中的属性为T类型; key=>value
type Record<K extends keyof any, T> = {
[P in K]: T;
}
Exclude<T, U>
从
T中排除可分配给U的类型
type Exclude<T, U> = T extends U ? never : T;
例子
// 结果:'b' | 'd'
type StringExclude = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>
Extract<T, U>
从
T中提取可分配给U的类型。Exclude的反操作
type Extract<T, U> = T extends U ? T : never;
例子
// 结果:'a' | 'c' type StringExtract = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>
NonNullable<T>
从
T中剔除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;
ReturnType<T>
获取函数返回值类型。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
InstanceType<T>
获取构造函数类型的实例类型
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
Parameters<T>
返回函数参数元组
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Omit
以一个类型为基础支持剔除某些属性,然后返回一个新类型。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;