typescript 面试常用高级用法

475 阅读5分钟

TypeScript 的类型系统会对数据进行类型检查,它可以在编译阶段规避不必要的错误,并且语义化清晰,有助于代码的阅读。

TypeScript 类型检查机制包含三个部分:

  • 类型推断
  • 类型保护
  • 类型兼容性

一、never 和 unknow

never 类型表示那些永远不存在的类型 return throw Error() 或则 不可取值

unknown 类型是 any 类型的对应的安全类型 unknown 只可以分配给any unknown

二、类型推断

不需要指定变量类型或函数的返回值类型,TypeScript 可以根据一些简单的规则推断其的类型

1、基础类型推断

初始化变量,设置默认参数和决定返回值时

2、最佳通用类型推断

从多个元素类型推断出一个类型时,TypeScript 会尽可能推断出一个兼容所有类型的通用类型 let x = [1, 'imooc', null] => let x: (string | number | null)[]

3、上下文类型推断

上下文类型推断则是从左向右的类型推断

class Animal {
  public species: string | undefined
  public weight: number | undefined
}

const simba: Animal = {
  species: 'lion',
  speak: true  // Error, 'speak' does not exist in type 'Animal'
}

三、类型断言

TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型, 覆盖它的推断, 慎用,编译时语法,不涉及运行时

1、关键字 as 覆盖其类型推断

2、 首尾标签<>

interface User {
nickname: string;
admin: boolean;
groups: number[]
}
const user = <User>{};

3、非空断言!

编译器不能够去除 null 或 undefined,可以使用非空断言 ! 手动去除

function fixed(name: string | null):string {
    return name!.charAt(0) || '';
}

4、双重断言 极少有应用场景

const user = 'Even' as any an User;

四、 类型兼容性

确定一个类型是否能赋值给其他类型

1、结构化

TypeScript 类型兼容性是基于结构类型的;结构类型只使用其成员来描述类型。

2、比较函数

判断两个函数是否兼容,首先要看参数是否兼容,第二个还要看返回值是否兼容。

3、枚举的类型兼容性

//同一个枚举 枚举与数字类型相互兼容
enum Status {
  Pending, 
  Resolved,
  Rejected
}
let current = Status.Pending
let num = 0;
current = num
num = current;
//枚举类型之间是不兼容
eunm Color {Red, Bulue, Green}

let current = Status.Pending
current = Color.Red // Error

4、类的类型兼容性

  • 比较两个类类型数据时,只有实例成员会被比较,静态成员和构造函数不会比较
  • 类的私有成员和受保护成员会影响兼容性
class Animal {
   feet!: number; // 实例成员  如feet 是受保护的,下面的赋值操作不可成功
   contructor(name:string, numFeet:number){}
}
class Size {
   feet!: number; // 实例成员
   contructor(numFeet:number){}
}
class Dog extends Animal {}
// 类 Animal 和类 Size 有相同的实例成员 `feat` 属性,且类型相同
let a:Animal
let s:Size
let d:Dog
//a = s!; //ok  feet 为protected feet!: number => Error
//s = a; //ok

// 父类之所以能够给赋值给子类,是因为子类中没有成员
//a = d! //ok
//d = a; //ok

5、范型的类型兼容性

如果没有指定泛型类型的泛型参数,会把所有泛型参数当成 any 类型比较. 当指定类型后泛型的类型兼容性根据其是否被成员使用而不同,被使用时赋值失败。

interface NotEmpty<T> {
  data: T // 如果不使用 ok
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y! // Error  //如果不使用data ok

let identity = function<T>(x: T): void {
  // ...
}
let identity1 = function<T>(y: T): void {
  // ...
}

identity = identity1 //ok

四、类型保护

类型保护是指缩小类型的范围,在一定的块级作用域内编译器推导其类型,提示并规避不合法的操作

1、 typeof 判断变量类型

if(type target === 'string'){
target.tofixed(2) // Error, is not number
target.split('')// true
}

2、instanceof

typeof 判断基础类型,instanceof 判断是否为某个对象的实例

3、 in 确定属性是否存在于某个对象

class User {
  public nickname: string | undefined
  public groups: number[];
}
function typeGuard(arg: User | Log) {
if('nickname' in arg) {
   arg.nickname = 'imooc';
}
}

五、infer 关键字 声明类型变量

在条件类型表达式中,可以在 extends 条件语句中使用 infer 关键字来声明一个待推断的类型变量 infer 的作用是让 TypeScript 自己推断,并将推断的结果存储到一个类型变量中,infer 只能用于 extends 语句中。

1、ReturnType 理解 infer

ReturnType<T> – 获取函数返回值类型

const add = (x:number, y: number) => x+y;
type t = ReturnType<typeof add> // type t = number;

来看一下 ReturnType 的实现源码:

type ReturnType<T extends (...args:any)=> any> = T extends (...args: any) => infer R ? R : any

如果 `T` 满足约束条件 `(...args: any) => any`TypeScript 推断出函数的返回值, 并且能够赋值给 `(...args: any) => infer R`, 借助 `infer` 关键字将其储存在类型变量 `R` 中,则返回类型为 `R`,否则为 any 类型

3. 借助 infer 实现元组转联合类型 [string, number] -> string | number

type Flatten<T> = T extends Array<infer U> ? U : never;

type T0 = [string, number]
type T1 = Flatten<T0> // string | number;

如果泛型参数 T 满足约束条件 Array<infer U>,那么就返回这个类型变量 U

七、接口interface 和 类型别名 type

都是对数据类型进行约束。

区别:

1、定义类型不同:

类型别名type可以作用于原始值(string、boolean等),联合类型(string|number),元组([string, number]) 交叉类型(string & number)以及其它任何你需要手写的类型.

interface 声明对象函数或者时,先定义接口,确保其数据结构的一致性 一般为一个对象结构。

2、扩展或继承方式不同

interface 可以实现extends(继承拥有) implements(打印 必须复制实现)

type 通过 联合 交叉 方式扩展

3、是否创建新类型

  • 类型别名并不会创建新类型,是对原有类型的引用,而接口会定义一个新类型。