[译]<<Effective TypeScript>> 技巧53:优先选择ECMAScript的特性而不是ts的特性

289 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧53:优先选择ECMAScript的特性而不是ts的特性

ts和js的关系一直在变。微软在2010年开始在ts投入精力。业界围绕js的普遍态度:这是一门充满问题的语言,需要修复。框架和源代码编译器给js添加很多功能是非常常见的。例如:类,装饰器,module。ts也不例外添加了:classes,enums,modules。

随着时间流逝,掌管js标准制定的TC39给js核心添加了很多功能。 这与ts已有的功能产生了冲突。ts开发组非常矛盾:到底是采用新的特性,还是打破现有代码?

ts开发组选择了后者,同时明确的治理原则:TC39定义js运行时,ts仅在类型空间进行创新。

ts依旧保留了部分部分功能。认识和理解这部分功能很重要,因为他们并不符合语言的其他部分。我建议大家避免使用这些功能,这样更能区分js和ts的关系。

Enums

很多语言用 enumerations 或者 enums定义一组值,ts也添加了enums:

enum Flavor {
  VANILLA = 0,
  CHOCOLATE = 1,
  STRAWBERRY = 2,
}

let flavor = Flavor.CHOCOLATE;  // Type is Flavor

Flavor  // Autocomplete shows: VANILLA, CHOCOLATE, STRAWBERRY
Flavor[0]  // Value is "VANILLA"

enums的理由是:相比于光秃秃的数字他们提供了更多的安全性,透明性。但是ts的enums依旧有一些怪癖:

  1. number类enum(例如Flavor),任何数字都可以对其赋值,这不够类型安全
  2. string类enum。它类型是安全的。也更透明。但是它不是结构化的。
  3. const类enum,不用于一般的enums,const类enum会在js运行时被擦除。例如将第一个例子中改为const enum Flavor。在js运行时Flavor.CHOCOLATE会被改写为0,这和我们预期不一样!同时这种行为在 numer类enum和string类enum会有差异。
  4. string类enum的居然是类型化的,这非常让然惊讶,一般ts会用结构化的类型来保证可分配性。
    enum Flavor {
      VANILLA = 'vanilla',
      CHOCOLATE = 'chocolate',
      STRAWBERRY = 'strawberry',
    }
    
    let flavor = Flavor.CHOCOLATE;  // Type is Flavor
        flavor = 'strawberry';
     // ~~~~~~ Type '"strawberry"' is not assignable to type 'Flavor'
    

当你发布一个库的时候,这会对你有所影响。家当你有一个函数需要传入一个Flavor:

function scoop(flavor: Flavor) { /* ... */ }

在js中你这样做是正确的:

scoop('vanilla');  // OK in JavaScript

但是在ts中不一样:

scoop('vanilla');
   // ~~~~~~~~~ '"vanilla"' is not assignable to parameter of type 'Flavor'

import {Flavor} from 'ice-cream';
scoop(Flavor.VANILLA);  // OK

这种割裂的体验对于开发者不友好,我们要避免使用string类enums。ts提供了替代方案:联合type:

type Flavor = 'vanilla' | 'chocolate' | 'strawberry';

let flavor: Flavor = 'chocolate';  // OK
    flavor = 'mint chip';
 // ~~~~~~ Type '"mint chip"' is not assignable to type 'Flavor'

这种方法不仅提供了类型安全,二姐还提供了自动补全功能:

function scoop(flavor: Flavor) {
  if (flavor === 'v
                   // Autocomplete here suggests 'vanilla'
}

Parameter Properties

我们常常在class初始化时候指定一个属性的值:

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

ts提供了一个更简洁的语法:

class Person {
  constructor(public name: string) {}
}

第二个例子被称作:Parameter Properties。这两个例子作用一样,但是有几个问题:

  • 这是少数几个ts结构,在编译成js代码后,会改变代码。
  • 上面的这儿参数name看起来像是未使用的参数
  • Parameter Properties 和 非Parameter Properties混合使用会隐藏你的代码设计
    class Person {
      first: string;
      last: string;
      constructor(public name: string) {
        [this.first, this.last] = name.split(' ');
      }
    }
    
    这个class有三个属性(first,last,name),但是猛地一看可能会以为只有两个属性。如果三个属性都是Parameter Properties,那么这个class看起来像interface

是否使用Parameter Properties是具有争议的,我的建议是尽量不用。特别是Parameter Properties和非Parameter Properties不要混用。

Namespaces and Triple-Slash Imports

ES2015之前,js没有自己的module系统。所以不同环境添加不同的module:

  • node.js: require 和 module.exports
  • AMD:define 和回调函数 ts也有相应的module 系统:module,triple-slash:
namespace foo {
  function bar() {}
}
/// <reference path="other.ts"/>
foo.bar();

ES2015之后,js有了官方的import,export系统,我们要坚持使用js官方的。

Decorators

Decorators 用来注释或者修改classes,method,properties,例如你可以定义一个logged,来输出函数调用信息:

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  @logged
  greet() {
    return "Hello, " + this.greeting;
  }
}

function logged(target: any, name: string, descriptor: PropertyDescriptor) {
  const fn = target[name];
  descriptor.value = function() {
    console.log(`Calling ${name}`);
    return fn.apply(this, arguments);
  };
}

console.log(new Greeter('Dave').greet());
// Logs:
// Calling greet
// Hello, Dave

Decorators设计用来支持Angluar框架,但是这项设计没有被TC39承认。所以使用Decorators是有风险的,除非你正在使用Angluar,或者Decorators被js标准承认,不要使用Decorators