Typescript高级操作

keyof

keyof操作符的作用是遍历一个对象/接口获取键值。
type Point = { x: number; y: number }; type P = keyof Point; // 相当于 // type P = 'x' | 'y' const p1:P = 123 // 报错 const p2:P = '888' // 报错 const p3:P = 'x' // 正确
如果使用索引类型
type Point = { [name: string]: number }; type P = keyof Point; // 相当于 // type P = number

typeof

在js中typeof主要作用是判定变量类型,而ts扩展了typeof关键字的作用,使其可以进行“读取类型”操作。
const a= 123 const b: typeof a = 123
这对于基本类型不是很有用,但与其他类型操作符结合使用,可以使用typeof方便地表示许多模式。例如,让我们从查看预定义的类型ReturnType开始。它接受函数类型并生成其返回类型:
function fn():string{ return '123' } type fnType = typeof fn; // type fnType = () => string let a: ReturnType<fnType>; a = '123' // string
为了避免混乱,ts限制typeof关键字后面只能跟标识符(变量名、函数名等)或对象属性。
// 错误 function fn():string{ return '123' } let a:typeof fn() = '123' // 正确 let o = { a: '123' } let a: typeof o.a = '123'

索引访问类型(Indexed Access Types)

ts可以通过索引访问类型。可以通过typeinterface对象来设置类型。
// 对象 let o = { a: '123' } let a: typeof o['a'] = '123' // 接口 interface O1 { a: string } type Oa = O1['a'] // 类型别名 type O2 = { a: string } type Ob = O2['a']
甚至你还可以使用联合类型。
type A = { a: string, b:number } type B = A['a' | 'b'] // string | number type C = A[keyof A] // string | number
 

Mapped Types

有些时候你可能要重复定义某些类型,如果你不想编写重复的代码,可以使用映射类型。
type A<Type> = { [Property in keyof Type]: boolean; }; type = { a: string; b: number; }; type O = A<FeatureFlags>; // {a:boolean;b:boolean}
这里使用到了泛型, [Property in keyof Type] 会迭代FeatureFlags 所有的key,你也可以让所有的key定义为原来的类型。
type A<Type> = { [Property in keyof Type]: Type[Property]; };
注意:在映射类型中不能定义属性和方法。
type A<Type> = { [Property in keyof Type]: Type[Property]; a: string; // 错误 fn:()=>void; // 错误 };
 

条件类型

很多时候我们需要根据输入值的类型来判断输出值的类型,这个时候就可以使用条件类型。
条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
SomeType extends OtherType ? TrueType : FalseType;
例如:
type A = number; type B = A extends number ? number : string const b:B = 1
条件类型配合泛型时就很有用了,例如我们需要实现一个函数,如果传入的参数是字符串则返回string,如果参数是数字类型则返回number,如果使用泛型+条件类型就可以这样写:
function fn<O extends string | number>(arg: O):O extends string ? 'string' : 'number'{ if(typeof arg === 'string'){ return 'string' as O extends string ? 'string' : 'number' }else { return 'number' as any; } }
可以将条件类型提取出来:
type FnReturnType<O extends string | number> = O extends string ? 'string' : 'number' function fn<O extends string | number>(arg: O): FnReturnType<O> { if(typeof arg === 'string'){ return 'string' as FnReturnType<O>; }else { return 'number' as FnReturnType<O>; } } fn<string>('1')
 

infer

infer需要搭配条件类型来使用,infer的作用是从正在比较的类型中推断类型,然后在 true  分支里引用该推断结果。
我们用下面这个例子来描述infer的作用,在下面这个例子中,我们实现ArrayItemType类型来获取数组元素的类型,如果T是一个数组则返回其元素的类型,否则返回null,我们可以这样写:
type ArrayItemType<T> = T extends any[] ? T[number] : null; const arr = [1]; const a: ArrayItemType<typeof arr> = 0;
这里运用了索引访问类型,如果使用infer,代码还能更简洁。
type ArrayItemType<T> = T extends Array<infer K> ? K : null; const arr = [1]; const a: ArrayItemType<typeof arr> = 0;
注意,infer推断的类型只能在条件类型的true分支中使用,不能在false分支使用,例如下面的代码:
// 正确的 type ArrayItemType<T> = T extends Array<infer K> ? null : K; // 错误的,K不能在false分支使用 type ArrayItemType<T> = T extends Array<infer K> ? null : K;

类型谓词

TypeScript的类型谓词是一个检查类型的函数,用于检查一个输入值是否为某个特定类型。它的语法如下:
function isString(type: unknown): type is string { return typeof type === 'string' }
类型谓词的作用在于它能够检查一个值是否为某个特定类型,并且如果检查成功,返回true,否则返回false。它可以用于类型检查,以确保代码的正确性。
function fn(x: unknown) { if (isString(x) /*判断x是否是string类型*/ ) { x.toUpperCase(); } }

非空断言

Typescript还有个很实用的操作符——非空断言,它想到于告诉Typescript这个变量肯定不会是undefined或null。
它的用法是变量后面跟个!符号。
例如:
let a: string|null; let b:string = a // error,因为a可能null let c:string = a! // ok
也可以用在对象的属性或方法上,就像可选链一样:
a!.b // a不为null或undefined
例如在vue的$refs中,有时我们明确知道ref引用了某个dom节点,例如this.$refs.linkRef,但是typescript会认为this.$refs.linkRef 有可能为undefined,这个时候就可以用非空断言,如this.$refs!.linkRef ,当然另一种写法是用可选链,因为在某些情况下(例如页面跳转)引用的dom节点确实会为空。
 

确定赋值断言

TypeScript 的确定赋值断言,允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。
let name!: string;
举个例子:
let a:string; fn() a.toLocaleLowerCase() function fn(){ a = '' }
在这个例子中,我们已经知道变量a已经被赋值,但是typescript没有检测到,因此我们需要用确定赋值断言明确地告诉

实用类型

typescript提供一些全局范围可用的类型,可以简化我们的一些操作。