Typescript 最佳实践

一年前刚接触 typescript 的时候, 以为它加大了代码工做量. 写一大堆东西.为了找某个类型东奔西跑, 引入第三库还常常报错. 然而如今的我想说: 真香. 咱们常常吐槽别人代码可维护性特别低, 老是但愿别人可以主动的写注释, 但是写注释却没有任何方式能够进行约束. 这下好了, 类型就是最好的注释, 用 typescript, 能够大大提升代码的可维护性.前端

一. 如何处理第三方库类型相关问题

Typescipt 所提供的第三方库类型定义不只约束咱们的输入调用, 还能为咱们提供文档. 如今, NPM 上的第三方类型定义种类繁多,很难保证类型定义是正确的. 也很难保证全部使用的第三方库都有类型定义. 那么, 在这个充满未知的过程当中,如何才能正确使用TypeScript中的第三方库呢? 下面列举了四种常见的没法正常工做的场景以及对应的解决方法:react

  • 库自己没有自带类型定义
  • 库自己没有类型定义, 也没有相关的@type
  • 类型声明库有误
  • 类型声明报错
  1. 库自己没有自带类型定义 查找不到相关的库类型. 举个栗子 webpack

    在初次将 react 改造支持 typescript 时, 想必不少人都会遇到 module.hot 报错. 此时只须要安装对应的类型库便可. 安装 @types/webpack-env

  2. 库自己没有类型定义, 也没有相关的@type 那只能本身声明一个了. 随便举个栗子.web

declare module “lodash”
复制代码
  1. 类型声明库有误
  • 推进解决官方类型定义的问题, 提issue, pr
  • Import 后经过 extends 或者 merge 能力对原类型进行扩展
  • 忍受类型的丢失或不可靠性
  • // @ts-ignore 忽略
  1. 类型声明报错
  • 在 compilerOptions 的添加"skipLibCheck": true, 曲线救国

二. 巧用类型收缩解决报错

下面列举了几种常见的解决方法:typescript

  • 类型断言
  • 类型守卫 typeof in instanceof 字面量类型保护
  • 双重断言

一、 类型断言 类型断言能够明确的告诉 TypeScript 值的详细类型, 在某些场景, 咱们很是确认它的类型, 即便与 typescript 推断出来的类型不一致. 那咱们可使用类型断言. 语法以下:bash

<类型>值
值 as 类型 // 推荐使用这种语法. 由于<>容易跟泛型, react 中的语法起冲突
复制代码

举个例子, 以下代码, padding 值能够是 string , 也能够是 number, 虽然在代码里面写了 Array(), 咱们明确的知道, padding 会被parseint 转换成 number 类型, 但类型定义依然会报错.app

function padLeft(value: string, padding: string | number) {
  // 报错: Operator '+' cannot be applied to 
  // types 'string | number' and 'number'
  return Array(padding + 1).join(" ") + value;
}
复制代码

解决方法, 使用类型断言. 告诉 typescript 这里我确认它是 number 类型, 忽略报错.函数

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;
}
复制代码

二、 类型守卫 类型守卫有如下几种方式, 简单的归纳如下post

  • typeof: 用于判断 "number","string","boolean"或 "symbol" 四种类型.
  • instanceof : 用于判断一个实例是否属于某个类
  • in: 用于判断一个属性/方法是否属于某个对象
  • 字面量类型保护

上面的例子中, 是 string | number 类型, 所以使用 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;
    }
}
复制代码

相比较 类型断言 as , 省去了大量代码. 除了 typeof , 咱们还有几种方式, 下面一一举例子.

  • 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);
    }
}
复制代码
  • in —-- 用于判断一个属性/方法是否属于某个对象
interface B {
    b: string;
}
interface A {
    a: string;
}
function foo(x: A | B) {
    if ('a' in x) {
        return x.a;
    }
    return x.b;
}
复制代码
  • 字面量类型保护

有些场景, 使用 in, instanceof, typeof 太过麻烦. 这时候能够本身构造一个字面量类型.

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 HTMLElement; 
  // Error: 'Event''HTMLElement' 中的任何一个都不能赋值给另一个
}
复制代码

若是你仍然想使用那个类型,你可使用双重断言。首先断言成兼容全部类型的any

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

三. 巧用 typescript 支持的js最新特性优化代码

  1. 可选链 Optional Chining
let x=foo?.bar.baz();
复制代码

typescript 中的实现以下:

var _a;
let x = (_a = foo) === null || _a === void 0 ? void 0 : _a.bar.baz();
复制代码

利用这个特性, 咱们能够省去写不少恶心的 a && a.b && a.b.c 这样的代码

  1. 空值联合 Nullish Coalescing
let x =foo ?? '22';
复制代码

typescript 中的实现以下:

let x = (foo !== null && foo !== void 0 ? foo : '22');
复制代码

四. 巧用高级类型灵活处理数据

typescript 提供了一些很不错的工具函数. 以下图

  • 类型索引

为了实现上面的工具函数, 咱们须要先了解如下几个语法: keyof : 获取类型上的 key 值 extends : 泛型里面的约束 T[K] : 获取对象 T 相应 K 的元素类型

type Partial<T> = {
    [P in keyof T]?: T[P]
}
复制代码

在使用 props 的时候, 有时候所有属性都是可选的, 若是一个一个属性写 ? , 大量的重复动做. 这种时候能够直接使用 Partial

Record 做为一个特别灵活的工具. 第一个泛型传入对象的key值, 第二个传入 对象的属性值.

type Record<K extends string, T> = {
    [P in K]: T;
}
复制代码

咱们看一下下面的这个对象, 你会怎么用 ts 声明它?

const AnimalMap = {
    cat: { name: '猫', title: 'cat' },
    dog: { name: '狗', title: 'dog' },
    frog: { name: '蛙', title: 'wa' },
};
复制代码

此时用 Record 便可.

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' },
};
复制代码
  • never, 构造条件类型

除了上面的几个语法. 咱们还能够用 never , 构造条件类型来组合出更灵活的类型定义.

语法:

never: 从未出现的值的类型

// 若是 T 是 U 的子类型的话,那么就会返回 X,不然返回 Y
构造条件类型 : T extends U ? X : Y 
复制代码
type Exclude<T, U> = T extends U ? never : T;

// 至关于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
复制代码
  • 更简洁的修饰符: - 与 + 能够直接去除 ? 将全部对象属性变成必传内容.
type Required<T> = { [P in keyof T]-?: T[P] };

// Remove readonly
type MutableRequired<T> = { -readonly [P in keyof T]: T[P] };  
复制代码
  • infer: 在 extends 条件语句中待推断的类型变量。
// 须要获取到 Promise 类型里蕴含的值
type PromiseVal<P> = P extendsPromise<infer INNER> ? INNER : P;
type PStr = Promise<string>;

// Test === string
type Test = PromiseVal<PStr>;
复制代码

五. 辨别 type & interface

在各大类型库中, 会看到形形色色的 type 和 interface . 然而不少人在实际中殊不知道它们的区别.

官网的定义以下:

An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.

An interface can have multiple merged declarations, but a type alias for an object type literal cannot.

从一张图看出它们两的区别:

建议: 能用 interface 实现,就用 interface , 若是不能才用 type.

六. 常量枚举

对于敏感的数据, 可以使用常量枚举的方式.

const enum learn { 
    math,
    language,
    sports
}
复制代码

在编译以后, 空空如也:

当咱们不须要一个对象, 但须要值的话, 就可使用这个方法 , 能够减小运行时的代码 ,以下:

往期精彩文章:

欢迎关注「前端加加」,认真学前端,作个有专业的技术人...

相关文章
相关标签/搜索