TypeScript类型记录(二)

180 阅读6分钟

接着上一篇Typescript类型记录(一) - 掘金 (juejin.cn)

类型进阶

3.1 类型查询

TypeScriptJavaScript中的typeof运算符进行了扩展,使其能够在表示类型的位置上使用。当在表示类型的位置上使用typeof运算符时,它能够获取操作数的类型,这就是类型查询。

const point = {x:0};
function sayHello(s:string):string{
    return 'hello '+s;
}

type T = typeof point; // {x: number}
type T1 = typeof sayHello; // (s:string)=>string

3.2 类型断言

TypeScript程序中的每一个表达式都具有某种类型,编译器可以通过类型注解或者类型推断来确定表达式的类型。但有些时候,开发者比编译器更加清楚某个表达式的类型。

3.2.1 T类型断言

<T>类型断言语法如下:

<T> expr

在该语法中,T表示类型断言的目标类型;expr表示一个表达式。<T>类型断言尝试将expr表达式的类型转换为T类型。

// 默认通过getElementById获取的元素是HTMLElement类型,该类型没有value属性
 const input = document.getElementById('input');
 if(input){
     const value = (<HTMLInputElement>input).value;
 }

3.2.2 as T类型断言

as T类型断言与<T>类型断言的功能完全相同,两者只是在语法上有所区别:

expr as T
// 默认通过getElementById获取的元素是HTMLElement类型,该类型没有value属性
 const input = document.getElementById('input');
 if(input){
     const value = (input as HTMLInputElement).value;
 }

注意: 在jsx语法中,使用<T>类型断言会有语法冲突,这个时候应该用as T类型断言

3.2.3 类型断言的约束

类型断言并不允许两个类型之间随意做转换,需要满足下面两个条件之一:

  • expr表达式的类型能够赋值给T类型
  • T类型能够赋值给expr表达式的类型

简单来说,类型断言时编译器会尝试进行双向的类型兼容性判定,允许将一个类型转换为更加精确的类型或者更加宽泛的类型。

在某些情况下,两个复杂的类型进行断言,可能会无法识别出正确的类型,因此错误地拒绝了类型断言操作。这个时候可以先将expr表达式转成unknown类型,再转换为目标类型。除了使用unknown类型外,也可以使用any类型,因为它们都属于顶端类型,任何类型都可以赋值。

expr as unknown as T

3.2.4 const类型断言

const类型断言能够将某一类型转换为不可变类型:

expr as const
//or
<const>expr

在该语法中,const是关键字,它借用了const声明的关键字;expr则要求是以下字面量中的一种:

  • boolean字面量
  • string字面量
  • number字面量
  • bigint字面量
  • 枚举成员字面量
  • 数组字面量
  • 对象字面量

具体的转换规则如下:

如果expr为boolean字面量、string字面量、number字面量、bigint字面量或枚举成员字面量,那么转换后的结果类型为对应的字面量类型:

let s1 = 'hello';//string
let s2 = 'hello' as const;// 'hello'

let n1 = 0; //number
let n2 = 0 as const; //0

let t1 = true; //boolean
let t2 = true as const; //true

enum Point {
    x,
    y
}

let p1 = Point.x; //Point
let p2 = Point.x as const; //Point.x

如果expr为数组字面量,那么转换后的结果类型为只读元组类型:

let arr1 = [1,2,3]; //number[]
let arr2 = [1,2,3] as const; //readonly [1,2,3]

如果expr为对象字面量,那么转换后的结果类型会将对象字面量中的属性全部转换成只读属性:

let obj1 = {x:0,y:0};//{x:number;y:number}
let obj2 = {x:0,y:0} as const;//{readonly x:0;readonly y:0}

3.2.5 !类型断言

非空类型断言!能够从某个类型中剔除undefined类型和null类型,它的语法如下所示:

expr!

当代码中使用了非空类型断言时,相当于在告诉编译器expr的值不是undefined值和null值:

function hello(str: string|undefined){
    const len = str!.length;
}

在严格模式下,会检查undefined和null值,如果上面不使用非空断言,编译器会认为strstring|undefined类型,导致报错。

3.3 类型细化

类型细化是指TypeScript编译器通过分析特定的代码结构,从而得出代码中特定位置上表达式的具体类型。

3.3.1 类型守卫

类型守卫是一类特殊形式的表达式,具有特定的代码编写模式。实质上,我们的代码中经常使用到它,比如typeofinstanceofin运算符逻辑与、或、非运算符等式表达式等等,这些都可以将类型细化。

除了内置的类型守卫之外,TypeScript允许自定义类型守卫函数:

x is T

在该语法中,x为类型守卫函数中的某个形式参数名;T表示任意的类型。

type A = {x:number}
type B = {y:number}

function isTypeA(x:A|B) x is A{
    return (x as A).x!==undefined;
}
function isTypeA(x:A|B) x is B{
    return (x as B).y!==undefined;
}

function fn(x:A|B){
    if(isTypeA(x)){
        ...
        // x == A;
    }
    if(isTypeB(x)){
        ...
        // x == B;
    }
}

3.3.2 可辨识联合类型

可辨识联合也叫作标签联合或变体类型,是一种数据结构,该数据结构中存储了一组数量固定且种类不同的类型,还存在一个标签字段,该标签字段用于标识可辨识联合中当前被选择的类型,在同一时刻只有一种类型会被选中。TypeScript中的可辨识联合类型由以下几个要素构成:

  • 一组数量固定且种类不同的对象类型。这些对象类型中含有共同的判别式属性,判别式属性就是可辨识联合定义中的标签属性。
  • 由可辨识对象类型组成的联合类型即可辨识联合,通常我们会使用类型别名为可辨识联合类型命名。
  • 判别式属性类型守卫。判别式属性类型守卫的作用是从可辨识联合中选取某一特定类型。
interface A {
    type:'A';
    length: number;
}

interface B {
    type:'B';
    name: string;
}

type T = A | B;
//联合类型T的判别式属性 是 type,可以通过它来判断具体类型
function fn(t:T){
    if(t.type === 'A'){
        ...
        //A
    }
    
    if(t.type === 'B'){
        ...
        // B
    }
}

3.3.3 断言函数

断言函数用于检查实际参数的类型是否符合类型判定。若符合类型判定,则函数正常返回;若不符合类型判定,则函数抛出异常。基于控制流的类型分析能够识别断言函数并进行类型细化。

function assert(x: unknown): asserts x is T { }
//or
function assert(x: unknown): asserts x { }

在该语法中,asserts x is Tasserts x表示类型判定,它只能作为函数的返回值类型。assertsis是关键字;x必须为函数参数列表中的一个形式参数名;T表示任意的类型;is T部分是可选的。若一个函数带有asserts类型判定,那么该函数就是一个断言函数。

对于asserts x is T形式的断言函数,它只有在实际参数x的类型为T时才会正常返回,否则将抛出异常。

对于asserts x形式的断言函数,它只有在实际参数x的值为真时才会正常返回,否则将抛出异常。

function assertIsNumber(x:unknown): asserts x is number {
    if(typeof x!=='number'){
        throw new TypeError('x should be number');
    }
    return undefined;
}

function assertTrue(x:unknown): asserts x {
    if(!x){
        throw new TypeError('x should be truthy value')
    }
}

在定义断言函数时,我们需要将函数的返回值类型声明为asserts类型判定。编译器将asserts类型判定视为void类型,这意味着断言函数的返回值类型是void。