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的產生:
- 該屬性本來就可以是
undefined
。這種情況下新增型別undefined - 屬性被間接初始化了(例如建構函式中呼叫一個方法,更改了屬性的值)。這種情況下我們可以使用 顯式賦值斷言 (修飾符號 !) 來幫助型別系統識別型別。後面具體介紹它,先看下程式碼中怎麼使用:
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
的作用:
- 預設開啟allowSyntheticDefaultImports(那是肯定的,我們需要它來實現預設匯入的功能)
- 名稱空間匯入不允許被呼叫或者構造,需要改成預設匯入
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 更好地展示錯誤資訊
看圖:
完。參考:ts官方