持续创作,加速成长!这是我参与「掘金日新计划 · 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依旧有一些怪癖:
- number类enum(例如Flavor),任何数字都可以对其赋值,这不够类型安全
- string类enum。它类型是安全的。也更透明。但是它不是结构化的。
- const类enum,不用于一般的enums,const类enum会在js运行时被擦除。例如将第一个例子中改为const enum Flavor。在js运行时Flavor.CHOCOLATE会被改写为0,这和我们预期不一样!同时这种行为在 numer类enum和string类enum会有差异。
- 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有三个属性(first,last,name),但是猛地一看可能会以为只有两个属性。如果三个属性都是Parameter Properties,那么这个class看起来像interfaceclass Person { first: string; last: string; constructor(public name: string) { [this.first, this.last] = name.split(' '); } }
是否使用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。