Typescript高级操作

tags: TypeScript Created time: December 24, 2022 8:42 PM emoji: https://www.typescriptlang.org/favicon-32x32.png?v=8944a05a8b601855de116c8a56d3b3ae

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提供一些全局范围可用的类型,可以简化我们的一些操作。

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type