zoukankan      html  css  js  c++  java
  • Typescript 最佳实践

    文章列表: 

    为了更好的阅读体验,  可以看. 

    一年前刚接触 Typescript 的时候, 觉得它加大了代码工作量. 写一大堆东西.为了找某个类型东奔西跑, 引入第三库还经常报错. 

    然而现在的我想说: 真香. 

    我们经常吐槽别人代码可维护性特别低, 总是希望别人能够主动的写注释, 可是写注释却没有任何方式可以进行约束. 这下好了, 类型就是最好的注释, 用 Typescript, 可以大大提高代码的可维护性. 

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

    Typescipt 所提供的第三方库类型定义不仅约束我们的输入调用, 还能为我们提供文档. 现在, NPM 上的第三方类型定义种类繁多,很难保证类型定义是正确的. 也很难保证所有使用的第三方库都有类型定义. 

    那么, 在这个充满未知的过程中,如何才能正确使用TypeScript中的第三方库呢?

    下面列举了四种常见的无法正常工作的场景以及对应的解决方法:

    • 库本身没有自带类型定义

    • 库本身没有类型定义, 也没有相关的@type

    • 类型声明库有误

    • 类型声明报错

    1. 库本身没有自带类型定义

    查找不到相关的库类型. 举个栗子 

    在初次将 react 改造支持 typescript 时, 想必很多人都会遇到 module.hot 报错. 此时只需要安装对应的类型库即可. 

    安装 @types/webpack-env

    2. 库本身没有类型定义, 也没有相关的@type

    那只能自己声明一个了. 随便举个栗子. 

    declare module "lodash"

    3. 类型声明库有误

    • 推动解决官方类型定义的问题, 提issue, pr 

    • Import 后通过 extends 或者 merge 能力对原类型进行扩展

    • 忍受类型的丢失或不可靠性

    • 使用 // @ts-ignore  忽略

    4. 类型声明报错

    • 在 compilerOptions 的添加"skipLibCheck": true, 曲线救国

    二. 巧用类型收缩解决报错下面列举了几种常见的解决方法:

    • 类型断言

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

    • 双重断言

    1、 类型断言

    类型断言可以明确的告诉 TypeScript 值的详细类型,

    在某些场景, 我们非常确认它的类型, 即使与 typescript 推断出来的类型不一致. 那我们可以使用类型断言. 

    语法如下: 

    <类型>值

    值 as 类型 // 推荐使用这种语法. 因为<>容易跟泛型, react 中的语法起冲突

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

    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;

    }

    2、 类型守卫

    类型守卫有以下几种方式, 简单的概括以下

    • 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);

       }

    }

    3、双重断言

    有些时候使用 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 Chaining 

    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 这样的代码

    2. 空值联合 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<State>  

    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 extends Promise<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. 

    为了更好的阅读体验,  《typescrit 最佳实践》

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

  • 相关阅读:
    mock数据
    关于适配各种浏览器的图片预览。
    闭包
    兼容性 适配
    递归 使用callee
    webservice的model层命名空间不同的问题
    删除右键菜单中的Git
    windows server core 设置shell 及切换
    设置共享用户名密码
    Windows Remote Shell(WinRM)使用介绍
  • 原文地址:https://www.cnblogs.com/beidan/p/12118198.html
Copyright © 2011-2022 走看看