TypeScript专题—技巧集锦

503 阅读5分钟

1、T[K] - 类型映射,获取类型的值

interface Person {
  name: string
  age: number
}
 
type V = Person[keyof Person]
// type V = "string" | "number"

2、ts-toolbelt —TS 版lodash库

import type { Object } from 'ts-toolbelt'
 
// 使对象的部分属性变为可选
type T1 = Object.Optional<{ a: string;b: number }, 'a'>
// type T1 = {
  // a?: string | undefined;
    // b: number;
// }
 
// 合并两个对象,前面对象为undefined 的属性被后面对象对应属性覆盖
type T2 = Object.MergeUp<{ a: 'a1', b?:'b1' }, { a: 'a2', b: 'b2' }>
// type T2 = {
    // a: "a1";
    // b: "b1" | "b2";
// }

3、假定对象的所有值都是数组类型, 如何获取对象值中的数组元素的类型

const data = {
  a: ['x', 'y', 'z'],
  b: [1, 2, 3]
} as const
 
type GetValueElementType<T extends { [key: string]: ReadonlyArray<any> }> = T[keyof T][number]
type TElement  = GetValueElementType<typeof data>

4、假定对象的值不都是数组类型,如何获取对象值中的数组元素的类型

实现1通过extends判断对象的值类型,通过数组下标获取元素类型

type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<any> ? T[K][number] : never }[keyof T]

实现2通过extends判断对象的值类型,通过infer推断,获取数组元素类型

type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<infer E> ? E : never }[keyof T]

5、实现一个函数getValue取得对象的value

可以使用keyof来增强getValue函数的类型功能:

function getValue<T extends Object, K extends keyof T>(o: T, key: K): T[K] {
    return o[key];
}
 
const obj1 = { name: '张三', age: 18 };
const a = getValue(obj1, 'hh');

6、in用于取联合类型的值,主要用于数组和对象的构造,切记不要用于interface

type name = 'firstName' | 'lastName';
type TName = {
    [key in name]: string;
};

7、获取参数this参数ThisParameterType

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

8、剔除this参数OmitThisParameter

type OmitThisParameter<T> = unknown extends ThisParameterType<T>
  ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;

9、找出T和U的差集和交集

type Diff<T, U> = T extends U ? never : T; // 找出T的差集
type Filter<T, U> = T extends U ? T : never; // 找出交集
 
type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // => "b" | "d"
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // => "a" | "c"

10、如何处理第三方库类型相关问题

【1】库本身没有自带类型定义,查找不到相关库类型,此时只需安装对应的类型库即可。

【2】库本身没有类型定义, 也没有相关的@type,此时需要自己声明一个declare module “lodash”。

【3】类型声明库有误,可以Import后通过extends或者merge能力对原类型进行扩展、// @ts-ignore忽略、忍受类型的丢失或不可靠性。

【4】类型声明报错,可以在compilerOptions的添加"skipLibCheck": true。

11、巧用类型收缩解决报错

类型断言:

function padLeft(value: string, padding: string | number) {
    // 正常
    return Array(padding as number + 1).join(" ") + value;
}

但是如果有下面这种情况, 我们要写很多个as么?

function padLeft(value: string, padding: string | number) {
  console.log((padding as number) + 3);
  console.log((padding as number) + 2);
  console.log((padding as number) + 5); 
  return Array((padding as number) + 1).join(' ') + value;
}

类型守卫typeof in instanceof字面量类型保护:

【1】typeof: 用于判断number、string、boolean、symbol四种类型。

上面的例子适合使用typeof来进行类型守卫:

function padLeft(value: string, padding: string | number) { 
  if (typeof padding === 'number') {     
    console.log(padding + 3); //正常    
    console.log(padding + 2); //正常    
    console.log(padding + 5); //正常    
    return Array(padding + 1).join(' ') + value; //正常
  }
  if (typeof padding === 'string') {      
    return padding + value;
  }
}

【2】instanceof : 用于判断一个实例是否属于某个类。

class Man {
    handsome = 'handsome';
}

class Woman {
    beautiful = 'beautiful';
}
 
function Human(arg: Man | Woman) {
  if (arg instanceof Man) {    
    console.log(arg.handsome);   
    console.log(arg.beautiful); // error
  } else {    
    // 这一块中一定是 Woman     
    console.log(arg.beautiful);
  }
}

【3】in: 用于判断一个属性/方法是否属于某个对象。

interface B {
    b: string;
}

interface A {
    a: string;
}

function foo(x: A | B) {
  if ('a' in x) {    
    return x.a;
  }
    return x.b;
}

【4】字面量类型保护。

type Man = {
  handsome: 'handsome';
  type: 'man';
};

type Woman = {
  beautiful: 'beautiful';
  type: 'woman';
};
 
function Human(arg: Man | Woman) {
  if (arg.type === 'man') {
    console.log(arg.handsome);
    console.log(arg.beautiful); // error
  } else {
    // 这一块中一定是 Woman
    console.log(arg.beautiful);
  }
}

双重断言:

有时候使用as也会报错,因为as断言是有条件的,它只有当S类型是T类型的子集,或者T类型是S类型的子集时,S能被成功断言成T。

function handler(event: Event) {
    const element = (event as any) as HTMLElement; // 正常
}

12、巧用typescript支持的js最新特性优化代码

【1】可选链

let x=foo?.bar.baz();
// 等价于
var _a;
let x = (_a = foo) === null || _a === void 0 ? void 0 : _a.bar.baz();

【2】空值联合

let x =foo ?? '22';
// 等价于
let x = (foo !== null && foo !== void 0 ? foo : '22');

13、巧用高级类型灵活处理数据

Record类型工具可用于声明一些比较复杂的数据结构。


const AnimalMap = {
  cat: { name: '猫', title: 'cat' },
  dog: { name: '狗', title: 'dog' },
  frog: { name: '蛙', title: 'wa' },
};
 
type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription { name: string, title: string }

const AnimalMap: Record<AnimalType, AnimalDescription> = {
  cat: { name: '猫', title: 'cat' },
  dog: { name: '狗', title: 'dog' },
  frog: { name: '蛙', title: 'wa' },
};

14、对于敏感的数据, 可使用常量枚举的方式,在编译之后, 空空如也

const enum learn { 
  math,
  language,
  sports
}

15、常量枚举不能包含计算成员

const enum Color {Red, Green, Blue = "blue".length};// error 不能包含计算成员

16、外部枚举

使用declare enum定义的枚举类型,只会用于编译时的检查,编译结果中会被删除,外部枚举与声明语句一样,常出现在声明文件中,同时使用declare和const也是可以的。

17、类与接口implements

不同的类之间某一部分可能行为一致,那么为了不重复写两个一样的接口,可以使用implements实现重用interface,implements可以理解为"实现"。

interface Behavior {
  eat(food: string): void
}
 
class Dog implements Behavior {
  eat(foot) {}   
}
class Cat implements Behavior {
  eat(foot) {}
}
 
class habaDog extends Dog implements Behavior {
  // 此时哈巴狗继承了狗类,就有了eat方法  
}

多个实现:

interface Behavior { // 行为接口
  eat(food: string): void
}
 
interface Appearance { // 外表接口
  fur:string
}
 
class Dog implements Behavior {
  eat(foot) {}   
}
 
class habaDog extends Dog implements Behavior, Appearance {
    fur = '' 
}

18、接口继承

interface Fa {
  surname: string
}
 
interface Son extends Fa {
  name: string
}
 
const obj: Son = {
  surname : 'z',
  name: 'zc'
}

19、接口继承class

class Fa {
  constructor() {}
  suck(){}
}
 
interface Son extends Fa {
  suck():void;
  name: string;
}