最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 编写高质量可维护的代码:Awesome TypeScript

    正文概述 掘金(政采云前端团队)   2021-01-06   453

    编写高质量可维护的代码:Awesome TypeScript

    编写高质量可维护的代码:Awesome TypeScript

    前言

    高质量可维护的代码应具备可读性高、结构清晰、低耦合、易扩展等特点。而原生的 JavaScript 由于其弱类型和没有模块化的缺点,不利于大型应用的开发和维护,因此,TypeScript 也就应运而生。

    TypeScript 是 JavaScript 的一个超集,它的设计初衷并不是为了替代 JavaScript,而是基于 JavaScript 做了一系列的增强,包括增加了静态类型、接口、类、泛型、方法重载等等。所以,只要你有一定的 JavaScript 功底,那么 TypeScript 上手就非常简单。并且,你可以在 TypeScript 中愉快的使用 JavaScript 语法。

    接下去,本文将给大家分享下,TypeScript 的重要特性以及在实际场景中的使用技巧,帮助大家更高效的编写高质量可维护的代码。

    Typescript VS Javascript

    JavaScript

    • JavaScript 是动态类型语言,在代码编译阶段不会对变量进行类型检测,从而会把潜在的类型错误带到代码执行阶段。并且在遇到不同类型变量的赋值时,会自动进行类型转换,带来了不确定性,容易产生 bug。
    • JavaScript 原生没有命名空间,需要手动创建命名空间,来进行模块化。并且,JavaScript 允许同名函数的重复定义,后面的定义可以覆盖前面的定义。这也给我们开发和维护大型应用带来了不便。

    TypeScript

    • TypeScript 是静态类型语言,通过类型注解提供编译时的静态类型检查。
      • 在代码编译阶段会进行变量的类型检测,提前暴露潜在的类型错误问题。并且在代码执行阶段,不允许不同类型变量之间的赋值。
      • 清晰的类型注解,不仅让代码的可读性更好,同时也增强了 IDE 的能力,包括代码补全、接口提示、跳转到定义等等。
    • TypeScript 增加了模块类型,自带命名空间,方便了大型应用的模块化开发。
    • TypeScript 的设计一种完全面向对象的编程语言,具备模块、接口、类、类型注解等,可以让我们的代码组织结构更清晰。

    经过上述对比,可以看到 TypeScript 的出现很好的弥补了 JavaScript 的部分设计缺陷,给我们带来了很大的便利,也提高了代码的健壮性和扩展性。

    重要特性

    数据类型

    • 基础数据类型包括:Boolean、Number、String、Array、Enum、Any、Unknown、Tuple、Void、Null、Undefined、Never。下面选择几个 TypeScript 特有的类型进行详解:

      • Enum 枚举:在编码过程中,要避免使用硬编码,如果某个常量是可以被一一列举出来的,那么就建议使用枚举类型来定义,可以让代码更易维护。
      // 包括 数字枚举、字符串枚举、异构枚举(数字和字符串的混合)。
      // 数字枚举在不设置默认值的情况下,默认第一个值为0,其他依次自增长
      enum STATUS {
        PENDING,
        PROCESS,
        COMPLETED,
      }
      let status: STATUS = STATUS.PENDING;  // 0
      
      • Any 类型:不建议使用。Any 类型为顶层类型,所有类型都可以被视为 any 类型,使用 Any 也就等同于让 TypeScript 的类型校验机制失效。
      • Unknown 类型:Unknown 类型也是顶层类型,它可以接收任何类型,但它与 Any 的区别在于,它首次赋值后就确定了数据类型,不允许变量的数据类型进行二次变更。所以,在需要接收所有类型的场景下,优先考虑用 Unknown 代替 Any。
        • Tuple 元组:支持数组内存储不同数据类型的元素,让我们在组织数据的时候更灵活。
      let tupleType: [string, boolean];
      tupleType = ["momo", true];
      
      • Void 类型:当函数没有返回值的场景下,通常将函数的返回值类型设置为 void。

    类型注解

    • TypeScript 通过类型注解提供编译时的静态类型检查,可以在编译阶段就发现潜在 Bug,同时让编码过程中的提示也更智能。使用方式很简单,在 : 冒号后面注明变量的类型即可。
    const str: string = 'abc';
    

    接口

    • 在面向对象编程的语言里面,接口是实现程序解耦的关键,它只定义具体包含哪些属性和方法,而不涉及任何具体的实现细节。接口是基于类之上,更进一步对实体或行为进行抽象,会让程序具备更好的扩展性。
    • 应用场景:比如我们在实现订单相关功能的时候,需要对订单进行抽象,定义一个订单的接口,包括订单基本信息以及对订单的相关操作,然后基于这个接口来做进一步的实现。后续如果订单的相关操作功能有变化,只需要重新定义一个类来实现这个接口即可。
    interface Animal {
    name: string;
    getName(): string;
    }
    class Monkey implements Padder {
    constructor(private name: string) {
      getName() {
        return 'Monkey: ' + name;
    	}
    }
    }
    

    • TypeScript 的类除了包括最基本的属性和方法、getter 和 setter、继承等特性,还新增了私有字段。私有字段不能在包含的类之外访问,甚至不能被检测到。Javascript 的类中是没有私有字段的,如果想模拟私有字段的话,必须要用闭包来模拟。下面用一些示例来说明下类的使用:
    • 属性和方法
    class Person {
    // 静态属性
    static name: string = "momo";
    // 成员属性
    gender: string;
    // 构造函数
    constructor(str: string) {
      this.gender = str;
    }
    // 静态方法
    static getName() {
      return this.name;
    }
    // 成员方法
    getGender() {
      return 'Gender: ' + this.gender;
    }
    }
    let person = new Person("female");
    
    • getter 和 setter
      • 通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据。
    class Person {
    private _name: string;
    get name(): string {
      return this._name;
    }
    set name(newName: string) {
      this._name = newName;
    }
    }
    let person = new Person('momo');
    console.log(person.name); // momo
    person.name = 'new_momo';
    console.log(person.name); // new_momo
    
    • 继承
    class Animal {
    name: string;
    constructor(nameStr=:string) {
      this.name = nameStr;
    }  
    move(distanceInMeters: number = 0) {
      console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
    }
    class Snake extends Animal {
    constructor(name: string) {
      super(name);
    } 
    move(distanceInMeters = 5) {
      super.move(distanceInMeters);
    }
    }
    let snake = new Snake('snake');
    snake.move(); // 输出:'snake moved 5m'
    
    • 私有字段
      • 私有字段以 # 字符开头。私有字段不能在包含的类之外访问,甚至不能被检测到。
    class Person {
    #name: string;
    constructor(name: string) {
      this.#name = name;
    }
    greet() {
     	console.log(`Hello, ${this.#name}!`);
    }
    }
    let person = new Person('momo');
    person.#name;   // 访问会报错
    

    ####泛型

    • 应用场景:当我们需要考虑代码的可复用性时,就需要用到泛型。让组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型。泛型允许同一个函数接受不同类型参数,相比于使用 Any 类型,使用泛型来创建的组件可复用和易扩展性要更好,因为泛型会保留参数类型。泛型可以应用于接口、类、变量。下面用一些示例来说明下泛型的使用:

    • 泛型接口

      interface identityFn<T> {
        (arg: T): T;
      }
      
    • 泛型类

      class GenericNumber<T> {
        zeroValue: T;
        add: (x: T, y: T) => T;
      }
      let myGenericNumber = new GenericNumber<number>();
      myGenericNumber.zeroValue = 0;
      myGenericNumber.add = function (x, y) {
        return x + y;
      };
      
    • 泛型变量

      使用大写字母 A-Z 定义的类型变量都属于泛型,常见泛型变量如下:

      • T(Type):表示一个 TypeScript 类型
      • K(Key):表示对象中的键类型
      • V(Value):表示对象中的值类型
      • E(Element):表示元素类型

    交叉类型

    • 交叉类型就是将多个类型合并为一个类型。通过 & 运算符定义。如下示例中,将 Person 类型和 Company 类型合并后,生成了新的类型 Staff,该类型同时具备这两种类型的所有成员。
    interface Person {
    name: string;
    gender: string;
    }
    interface Company {
    companyName: string;
    }
    type Staff = Person & Company;
    const staff: Staff = {
    name: 'momo',
    gender: 'female',
    companyName: 'ZCY'
    };
    

    联合类型

    • 联合类型就是由具有或关系的多个类型组合而成,只要满足其中一个类型即可。通过 | 运算符定义。如下示例中,函数的入参为 string 或 number 类型即可。
    function fn(param: string | number): void {
      console.log("This is the union type");
    }
    

    类型保护

    • 类型保护就是在我们已经识别到当前数据是某种数据类型的情况下,安全的调用这个数据类型对应的属性和方法。常用的类型保护包括 in 类型保护、typeof 类型保护、instanceof 类型保护和 自定义 类型保护。具体见以下示例:

      • in 类型保护
      interface Person {
        name: string;
        gender: string;
      }
      interface Employee {
        name: string;
        company: string;
      }
      type UnknownStaff = Person | Employee;
      function getInfo(staff: UnknownStaff) {
        if ("gender" in staff) {
          console.log("Person info");
        }
        if ("company" in staff) {
          console.log("Employee info");
        }
      }
      
      • typeof 类型保护
      function processData(param: string | number): unknown {
      	if (typeof param === 'string') {
        	return param.toUpperCase()
        }
        return param;
      }
      
      • instanceof 类型保护:和 typeof 类型用法相似,它主要是用来判断是否是一个类的对象或者继承对象的。
      function processData(param: Date | RegExp): unknown {
      	if (param instanceof Date) {
        	return param.getTime();
        }
        return param;
      }
      
      • 自定义 类型保护:通过类型谓词 parameterName is Type 来实现自定义类型保护。如下示例,实现了接口的请求参数的类型保护。
      interface ReqParams {
      	url: string;
       	onSuccess?: () => void;
       	onError?: () => void;
      }
      // 检测 request 对象包含参数符合要求的情况下,才返回 url
      function validReqParams(request: unknown): request is ReqParams {
      	return request && request.url
      }
      

    开发小技巧

    • 需要连续判断某个对象里面是否存在某个深层次的属性,可以使用 ?.
    if(result && result.data && result.data.list) // JS
    if(result?.data?.list) // TS
    
    • 联合判断是否为空值,可以使用 ??
    let temp = (val !== null && val !== void 0 ? val : '1'); // JS
    let temp = val ?? '1'; // TS
    
    • 不要完全依赖于类型检查,必要时还是需要编写兜底的防御性代码。

      • 因为类型报错不会影响代码生成和执行,所以原则上还是会存在 fn('str') 调用的可能性,所以需要 default 进行兜底的防御性代码。
    function fn(value:boolean){
    	switch(value){
      	case true: 
        	console.log('true');
          break;
        case false: 
          console.log('false');
          break;
        default: 
          console.log('dead code');
      }
    }
    
    • 对于函数,要严格控制返回值的类型.
    // 推荐写法
    function getLocalStorage<T>(key: string): T | null {
      const str = window.localStorage.getItem(key);
      return str ? JSON.parse(str) : null;
    }
    const data = getLocalStorage<DataType>("USER_KEY");
    
    • 利用 new() 实现工厂模式

      • TypeScript 语法实现工厂模式很简单,只需先定义一个函数,并声明一个构造函数的类型参数,然后在函数体里面返回 c 这个类构造出来的对象即可。以下示例中,工厂函数构造出来的是 T 类型的对象。
    function create<T>(c: { new(): T }): T {
    	return new c();
    }
    class Test {
      constructor() {
      }
    }
    create(Test);
    
    • 优先考虑使用 Unknown 类型而非 Any

    • 使用 readonly 标记入参,保证参数不会在函数内被修改

    function fn(arr:readonly number[] ){
      let sum=0, num = 0;
      while((num = arr.pop()) !== undefined){
        sum += num;
      }
      return sum;
    }
    
    • 使用 Enum 维护常量表,实现更安全的类型检查
    // 使用 const enum 维护常量
    const enum PROJ_STATUS {
      PENDING = 'PENDING',
      PROCESS = 'PROCESS',
      COMPLETED = 'COMPLETED'
    }
    function handleProject (status: PROJ_STATUS): void {
    }
    handleProject(PROJ_STATUS.COMPLETED)
    
    • 建议开启以下编译检查选项,便于在编译环境发现潜在 Bug
    {
      "compilerOptions": {
        /* 严格的类型检查选项 */
        "strict": true,                    // 启用所有严格类型检查选项
        "noImplicitAny": true,             // 在表达式和声明上有隐含的 any类型时报错
        "strictNullChecks": true,          // 启用严格的 null 检查
        "noImplicitThis": true,            // 当 this 表达式值为 any 类型的时候,生成一个错误
        "alwaysStrict": true,              // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
        
        /* 额外的检查 */
        "noUnusedLocals": true,            // 有未使用的变量时,抛出错误
        "noUnusedParameters": true,        // 有未使用的参数时,抛出错误
        "noImplicitReturns": true,         // 并不是所有函数里的代码都有返回值时,抛出错误
        "noFallthroughCasesInSwitch": true,// 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
      }
    }
    

    相关 VSCode 插件推荐

    TypeScript Extension Pack,它集合了我们日常常用的 TypeScript 相关插件:

    • TSLint:自动检测和修复不符合规范的 TypeScript 代码。
    • TypeScript Hero:对 import 引入模块顺序进行排序和组织 ,移除未被使用的。MacOS 上快捷键 Ctrl+Opt+o,Win/Linux 上快捷键 Ctrl+Alt+o
    • json2ts:将剪切板中的 JSON 转化成 TypeScript 接口。MacOS 上快捷键 Ctrl+Opt+V,Win/Linux 上快捷键 Ctrl+Alt+V
    • Move TS:在移动 TypeScript 文件或者包含 TypeScript 文件的文件夹时,会自动更新相关依赖模块的 import 路径。
    • Path Intellisense:路径和文件名的自动提示补全功能。
    • TypeScript Importer:import 引入模块时,自动搜索当前 workspace 下所有 export 的模块,并自动进行提示补全。
    • Prettier - Code formatter:格式化代码。

    推荐阅读

    前端工程师的自我修养:React Fiber 是如何实现更新过程可控的

    V8 引擎垃圾回收与内存分配

    招贤纳士

    政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

    如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com

    编写高质量可维护的代码:Awesome TypeScript


    起源地下载网 » 编写高质量可维护的代码:Awesome TypeScript

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元