TypeScript 2.7 记录

TypeScript 2.7版本记录

针对ts 2.7版本的特性作专门的实例,希望能加深理解。实例github地址 官方日志文档

增加常量声明的属性的支持(Constant-named properties)

对于常量,有更加智能的提示

const Foo = "Foo";
const Bar = "Bar";

let x = { // 2.7版本之前,x的类型为{[x:string]:string|number},现在为 {[Foo]: number;[Bar]: string;}}
    [Foo]: 100,
    [Bar]: "hello",
};

let a = x[Foo]; // has type 'number'
let b = x[Bar]; // has type 'string'

typescript 增加一种新的类型声明:unique symbols,是 symbols的子类型,仅可通过调用 Symbol()或 Symbol.for()或由明确的类型注释生成

ES6 引入的 Symbol 机制,Symbol是js的第七种数据类型,可以产生独一无二的值,可以用来保证每个属性的名字都是独一无二,从根本上防止属性名的冲突。结合ts,我们可以这样声明一个symbol,const Foo: unique symbol = Symbol()

unique symbol 类型必须由 const 关键字声明

// Error! 'Bar' isn't a constant.
let Bar: unique symbol = Symbol();

引用赋值一个 unique symbol 类型的值时候使用 typeof 操作符

// let Baz = Foo // 这样的话 Baz 的类型为symbol
let Baz: typeof Foo = Foo // 类型为unique symbol
// 在class里面使用 `unique symbol` 定义类的静态属性(不能用在类属性) 时,
// 需要使用 `readonly static` 关键字来声明
class C {
    static readonly StaticSymbol: unique symbol = Symbol();
}

当Symbol + const时,值的类型自动判断为unique symbol

// let SERIALIZE = Symbol("serialize-method-key"); SERIALIZE 类型为symbol
const SERIALIZE = Symbol("serialize-method-key");  // SERIALIZE 类型为unique symbol

接口中的计算属性名称引用必须引用类型为文本类型或 "unique symbol"

interface Serializable {
   // [("serialize-method-key")](obj: {}): string; //error
   [SERIALIZE](obj: {}): string;
}

class JSONSerializableItem implements Serializable {
   //  error 只能用引入的SERIALIZE来作属性的名称
   // ["serialize-method-key"](obj: {}) {
   //     return JSON.stringify(obj);
   // }
   [SERIALIZE](obj: {}) {
       return JSON.stringify(obj);
   }
}

另外,两个unique symbol类型的值不能互相比较(当然除非其中一个值的类型为用 typeof 另外一个值)

新编译选项,更严格的类属性检查( --strictPropertyInitialization)

TypeScript 2.7引入了一个新的控制严格性的标记 --strictPropertyInitialization

现在,如果开启 strictPropertyInitialization,我们必须要确保每个实例的属性都会初始值,可以在构造函数里或者属性定义时赋值。

class StrictClass {
    foo: number;
    bar = 'hello';
    baz: boolean; 
    // error,Property 'baz' has no initializer and is not definitely assigned in the constructor
    constructor() {
        this.foo = 42;
    }
}

有两种情况下我们不可避免该error的产生:

  1. 该属性本来就可以是 undefined 。这种情况下添加类型undefined
  2. 属性被间接初始化了(例如构造函数中调用一个方法,更改了属性的值)。这种情况下我们可以使用 显式赋值断言 (修饰符号 !) 来帮助类型系统识别类型。后面具体介绍它,先看下代码中怎么使用:
class StrictClass {
    // ...
    baz!: boolean;
    // ^
    // 注意到这个!标志
    // 代表着显式赋值断言修饰符
}

显式赋值断言(Definite Assignment Assertions)

尽管我们尝试将类型系统做的更富表现力,但我们知道有时用户比TypeScript更加了解类型

跟上面提到的类属性例子差不多,我们无法在给一个值赋值前使用它,但如果我们已经确定它已经被赋值了,这个时候类型系统就需要我们人的介入

let x: number;
initialize();
console.log(x + x);
//          ~   ~
// Error! Variable 'x' is used before being assigned.

function initialize() {
    x = 10;
}

添加 ! 修饰:let x!: number,则可以修复这个问题

我们也可以在表达式中使用!,类似 variable as string<string>variable :

let x: number;
initialize();
console.log(x! + x!); //ok

function initialize() {
    x = 10;
}

添加 --esModuleInterop 对ES模块和老式代码更好的互通

这一块中文官网花了挺长篇幅来讲这个内容,我也是实践过才明白讲的是什么,需要理解 __esModule 标记是做什么的、es6模块经过ts转换成commonjs是怎么的、 如何保持与老式代码( CommonJS/AMD/UMD)的互通性

先说一下 --esModuleInterop 的作用:

  1. 默认开启allowSyntheticDefaultImports(那是肯定的,我们需要它来实现默认导入的功能)
  2. 命名空间导入不允许被调用或者构造,需要改成默认导入
import * as express from "express";
// error 正确的实现导入方式应该是下面这种

import express from "express";
express();
注意: 我们强烈建议开启esModuleInterop,不管在新代码或者是老代码上。但该模式下会可能对已有的代码产生破环,对已有的命名空间导入(import * as express from "express"; express();)改成默认导入(import express from "express"; express(); )

让我们更深入理解,在 --esModuleInterop 下,ts对 import * 和 import default两种导入方式用两个helpers __importStar and __importDefault做分别处理。

构建前的代码:

import * as foo from "foo";
import b from "bar";
const a = 'newM'
export default a

构建后的代码:

"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
}
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
}
exports.__esModule = true;
var foo = __importStar(require("foo"));
var bar_1 = __importDefault(require("bar"));
const a = 'newM';
exports.default = a;

这样做的结果是,我们新的代码不管是通过ts,Babel或Webpack来构建,都能通过 __esModule 来判断是否为es模块,如果无__esModule,则创建含default的对象保存模块,这样就完成我们要的默认导入功能,同时保持对老的库的支持

固定长度元祖

[number, string, string]类型的值 不可赋值 [number, string] 类型的值了。

[number, string]类型等同于下面的 NumStrTuple声明:

interface NumStrTuple extends Array<number | string> {
    0: number;
    1: string;
    length: 2; // using the numeric literal type '2'
}

NumStrTuple 代表类型为固定长度为2,[0]为number类型,[1]为string 类型的数组

如果不希望固定宽度,只需要最小长度,可以这样:

interface MinimumNumStrTuple extends Array<number | string> {
    0: number;
    1: string;
}

更智能的对象字面量推断

// 现在能正常判断obj的类型了,而不是之前的 {}
const obj = test ? { text: "hello" } : {};  // { text: string } | { text?: undefined }
const s = obj.text;  // string | undefined

// { a: number, b: number } |
// { a: string, b?: undefined } |
// { a?: undefined, b?: undefined }
let obj2 = [{ a: 1, b: 2 }, { a: "abc" }, {}][0];

declare function f<T>(...items: T[]): T; 

// { a: number, b: number } |
// { a: string, b?: undefined } |
// { a?: undefined, b?: undefined }
let obj3 = f({ a: 1, b: 2 }, { a: "abc" }, {});

其它

in操作符细化和精确的 instanceof

in 操作符 和 instanceof 运算符 更好用了(就是那么简单)

--watch,--pretty 编译

--watch 会在重新编译后清空控制台
--pretty 更好地展示错误信息

看图:

TypeScript 2.7 记录


完。参考:ts官方

相关推荐