TypeScript 2.7 記錄

lindong發表於2018-05-18

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官方

相關文章