這篇博文快速介紹 TypeScript 靜態型別的標註。
你將學習什麼
閱讀本文後,您應該能夠理解以下程式碼的含義。
interface Array<T> {
concat(...items: Array<T[] | T>): T[];
reduce<U>(
callback: (state: U, element: T, index: number, array: T[]) => U,
firstState?: U): U;
···
}
複製程式碼
如果你認為這很神祕,那麼我同意你的看法。 但是(正如我希望證明的),這個標註相對來說容易學習。 一旦你理解了它,它會告訴你,一個直接,精確和全面的總結這些程式碼行為。 無需閱讀英文長篇描述。
嘗試一下程式碼示例
TypeScript 有一個線上編譯。 為了獲得最全面的檢查,應該開啟 “option” 選單中的所有內容。 這相當於在 --strict 模式下執行TypeScript編譯器。
設定全部的型別檢查
我總是使用 TypeScript 以最全面的設定 --strict。 沒有它,程式的編寫會變得相對容易些,但是你也會失去靜態型別檢查的許多好處。 目前,此設定啟用以下子設定:
- --noImplicitAny: 如果 TypeScript 無法推斷出某種型別,說明你沒有啟用這個選項。 這主要適用於函式和方法的引數:使用此設定,你就必須標註它們的型別。
- --noImplicitThis: 如果這個型別不明確,提示警告。
- --alwaysStrict: 儘可能使用 JavaScript 嚴格模式。
- --strictNullChecks: null 不是任何型別的一部分(除了它自己的型別,null),並且如果它是可接受的值,則必須明確給值。
- --strictFunctionTypes: 強化函式型別檢查。
- --strictPropertyInitialization: 如果某個屬性的值未定義,那麼它必須在建構函式中初始化。
更多資訊:TypeScript手冊中的“編譯器選項”一章。
型別
在這篇博文中,型別是一組值。 JavaScript 語言(不是TypeScript!)有7種型別:
- Undefined: 元素為未定義的集合。
- Null: 元素為 null 的集合。
- Boolean: 元素為 false 或 true 的集合。
- Number: 元素為數字的集合。
- String: 元素為字串的集合。
- Symbol: 元素為 symbols 的集合。
- Object: 元素為物件(包括函式和陣列)的集合.
所有這些型別都是動態的:您可以在執行時使用它們。
TypeScript 在 JavaScript 基礎上增加了一層:靜態型別。它只存在於編譯的時候或者原始碼型別檢查的時候。每一個有靜態型別的儲存(變數或者屬性)地方都可以預知它的值。型別檢查確保實現型別推斷,不用執行程式碼就能進行靜態型別檢查。舉個例子,如果函式f(x)的引數x具有靜態數字型別,則函式呼叫f('abc')是非法的,因為引數'abc'是錯誤的靜態型別引數。
型別標註
型別標註在變數的冒號後面。冒號後面的靜態型別標註描述了這個變數可以有什麼值。下面一個例子表示這個變數只能儲存數字型別。
let x: number;
複製程式碼
你可能想知道如果 x 沒有被初始化的時候是不是可以通過靜態型別檢查。對於這個問題, TypeScript 在給它賦值之前不會讓你讀取 x 。
型別推斷
在 TypeScript 中,即使所有變數你都寫靜態型別,但你不需要全部明確寫它的靜態型別。 TypeScript 經常可以推斷出它。 例如,如果你寫
let x = 123;
複製程式碼
然後TypeScript推斷x具有數字靜態型別。
描述型別
型別表示式在型別標註的冒號之後出現。 這些範圍從簡單到複雜,建立如下
合法的型別表示式有基本型別:
- JavaScript 動態型別的靜態型別:undefined,null,boolean,number,string,symbol,object
- TypeScript 特定型別:any(所有值的型別)等
注意,“未定義為值”和“未定義為型別”都被定義為未定義。 根據你使用它的地方,它作為一個值或者一個型別。 null也是一樣。
可以通過型別運算子組合基本型別來建立更多型別表示式,型別運算子與運算子 union(∪) 和 intersection(∩) 組合類似。
接下來解釋 TypeScript 提供的一些型別運算子。
陣列作為列表
有兩種方法可以表示 Array arr 其元素都是數字的列表:
let arr: number[] = [];
let arr: Array<number> = [];
複製程式碼
通常情況下,如果有給值,TypeScript可以推斷變數的型別。 在這種情況下,你實際上必須明確標註型別,因為對於空陣列,它無法確定元素的型別。
稍後,講解尖括號表示法(Array)。
陣列作為元組
如果您在陣列中儲存兩個點,那麼你將該陣列用作元組。 這看起來如下:
let point: [number, number] = [7, 5];
複製程式碼
在這種情況下,不需要型別註釋。 元組的另一個例子是 Object.entries(obj) 的結果:對於 obj 的每個屬性都有一個 [key,value] 對的陣列。
> Object.entries({a:1, b:2})
[ [ 'a', 1 ], [ 'b', 2 ] ]
複製程式碼
Object.entries() 的型別是:
Array<[string, any]>
複製程式碼
函式型別
這是一個函式型別的例子:
(num: number) => string
複製程式碼
該型別接受單個數字型別引數,和返回字串型別的值。 讓我們在型別註釋中使用這個型別(字串在這裡用作函式):
const func: (num: number) => string = String;
複製程式碼
同樣,我們通常不會在這裡使用型別註釋,因為 TypeScript 知道 String 的型別,因此可以推斷出 func 的型別。 下面的程式碼是一個更實用的例子:
function stringify123(callback: (num: number) => string) {
return callback(123);
}
複製程式碼
我們使用函式型別來描述 stringify123() 的回撥函式。 由於此型別註釋,TypeScript 拒絕以下函式呼叫。
f(String);
複製程式碼
但它接受以下函式呼叫:
f(Number);
複製程式碼
(原文在這可能順序寫反了)
函式結果型別宣告
標註函式的所有引數型別是一種很好的實踐。 你也可以指定結果型別(但 TypeScript 很適合推斷它):
function stringify123(callback: (num: number) => string): string {
const num = 123;
return callback(num);
}
複製程式碼
特殊結果型別 void
void 是函式結果的特殊型別:它告訴 TypeScript 函式總是返回 undefined (顯式或隱式):
function f1(): void { return undefined } // OK
function f2(): void { } // OK
function f3(): void { return 'abc' } // error
複製程式碼
可選引數
識別符號後面的問號表示該引數是可選的。 例如:
function stringify123(callback?: (num: number) => string) {
const num = 123;
if (callback) {
return callback(num); // (A)
}
return String(num);
}
複製程式碼
如果您在 --strict 模式下執行 TypeScript ,則會在檢查回撥沒有被省略的情況下讓你在 A 行中進行函式呼叫。
引數預設值
TypeScript 支援 ES6引數預設值:
function createPoint(x=0, y=0) {
return [x, y];
}
複製程式碼
預設值使引數可選。 通常可以省略型別註釋,因為 TypeScript 可以推斷型別。 例如,它可以推斷x和y都具有數字型別。
如果你想新增型別註釋,那看起來如下。
function createPoint(x:number = 0, y:number = 0) {
return [x, y];
}
複製程式碼
剩餘型別
您還可以使用 ES6 rest 運算子來處理 TypeScript 引數定義。 相應引數的型別必須是Array:
function joinNumbers(...nums: number[]): string {
return nums.join('-');
}
joinNumbers(1, 2, 3); // '1-2-3'
複製程式碼
聯合型別
在JavaScript中,變數常常是幾種型別之一。 要描述這些變數,你可以使用聯合型別。 例如,在以下程式碼中,x 的型別為 null 或型別 number :
let x = null;
x = 123;
複製程式碼
x 的型別可以被描述為 null | number :
let x: null|number = null;
x = 123;
複製程式碼
型別表示式 s | t 的結果是 型別s 和 t 的集合論聯合(正如我們前面所看到的那樣,這兩個集合)。
讓我們重寫函式 stringify123() :這一次,我們不希望引數回撥是可選的。 應該總是傳遞該引數。 如果呼叫者不想提供函式,他們必須顯式傳遞null。 這是實現如下:
function stringify123(
callback: null | ((num: number) => string)) {
const num = 123;
if (callback) { // (A)
return callback(123); // (B)
}
return String(num);
}
複製程式碼
注意,實際上我們必須檢查回撥是否是一個函式(A行),然後才能在B行進行函式呼叫。如果沒有檢查,TypeScript將報告錯誤。
? 與 undefined| T
型別T的可選引數? 和型別為 undefined | T 的引數非常相似。 (順便說一句,對於可選屬性也是如此。)
主要區別是可以省略可選引數:
function f1(x?: number) { }
f1(); // OK
f1(undefined); // OK
f1(123); // OK
複製程式碼
但是你不能省略型別為 undefined| T 的引數:
function f1(x?: number) { }
f1(); // OK
f1(undefined); // OK
f1(123); // OK
複製程式碼
在型別中,通常不包含 null 和 undefined 值
在許多程式語言中, null 是所有型別的一部分。 例如,在 java 中,只要引數的型別是 String ,就可以傳遞 null 並且 Java 不會報錯
相比之下,在 TypeScript 中,undefined 和 null 由不同的型別處理。 如果你想跟上述一樣,你需要一個型別聯合,比如 undefined | number 和 null | number 。
物件型別
與陣列類似,物件在 JavaScript 中扮演兩個角色( 偶爾混合 和 或更動態 ):
- 記錄:開發時已知的固定數量的屬性。 每個屬性可以有不同的型別。
- 字典:在開發時不知道名稱的任意數量的屬性。 所有屬性鍵(字串和/或符號)具有相同的型別,屬性值也是如此。
我們將在此文中忽略物件作為詞典。 另外,無論如何,Maps 對於字典來說通常是更好的選擇。
通過 interfaces 描述物件型別作為記錄
interfaces 描述物件型別。 例如:
interface Point {
x: number;
y: number;
}
複製程式碼
TypeScript的型別系統的一大優點是它是結構化的,而不是字面化。 也就是說, interface 匹配具有適當結構的所有物件:
function pointToString(p: Point) {
return `(${p.x}, ${p.y})`;
}
pointToString({x: 5, y: 7}); // '(5, 7)'
複製程式碼
相比之下,Java的型別系統需要類來實現介面。
可選屬性
如果一個屬性可以被省略,你在變數後面加一個問號:
interface Person {
name: string;
company?: string;
}
複製程式碼
函式
Interfaces 也可包含函式:
interface Point {
x: number;
y: number;
distance(other: Point): number;
}
複製程式碼
型別變數和泛型型別
使用靜態型別,有兩個級別:
- 物件級別
- 元型別級別
相似於
- 物件儲存正常變數
- 描述型別的變數,也是值型別的變數
正常變數通過 const,let 等引入。 型別變數通過尖括號(<>)引入。 例如,以下程式碼包含通過引入的型別變數T.
interface Stack<T> {
push(x: T): void;
pop(): T;
}
複製程式碼
你可以看到型別 引數T 在 Stack中 出現兩次。 所以這個介面可以直觀地理解如下:
- Stack 是一堆所有具有給定 型別T 的值。每當提到 Stack 時,您必須填寫T. 接下來我們將看到如何。
- push 函式 接受 T型別引數
- pop 函式 返回 T型別值
如果你使用 Stack ,你就需要給定 T的值。 以下程式碼顯示了一個虛介面 Stack ,其唯一目的是匹配介面。
const dummyStack: Stack<number> = {
push(x: number) {},
pop() { return 123 },
};
複製程式碼
示例:Maps
TypeScript 定義 Map 型別。例如:
const myMap: Map<boolean,string> = new Map([
[false, 'no'],
[true, 'yes'],
]);
複製程式碼
泛型函式
函式(和方法)也可以引入型別變數:
function id<T>(x: T): T {
return x;
}
複製程式碼
使用如下
id<number>(123);
複製程式碼
由於型別推理,可以省略型別引數
id(123);
複製程式碼
型別引數的傳遞
function fillArray<T>(len: number, elem: T) {
return new Array<T>(len).fill(elem);
}
複製程式碼
不必顯式指定 Array 的型別T - 它是從引數 elem 推斷出來的:
const arr = fillArray(3, '*');
// Inferred type: string[]
複製程式碼
結論
讓我們用我們所學的知識來理解我們之前看到的那段程式碼:
interface Array<T> {
concat(...items: Array<T[] | T>): T[];
reduce<U>(
callback: (state: U, element: T, index: number, array: T[]) => U,
firstState?: U): U;
···
}
複製程式碼
這是一個陣列的 interface ,其元素的型別為 T ,我們必須在使用此 interface 時填寫它們:
方法 .concat() 有零個或更多引數(通過剩餘引數操作符)。他們中的每個函式有型別 T[] 或 T 。因此,它可能是型別T 的陣列或者是單個 T 的值。
方法 .reduce() 引進了它自己的型別變數,U 。 U 表示以下實體全都具有相同型別(不需要指定,它會自動推斷):
- 回撥函式裡的 state 引數
- 回撥函式裡的結果
- 方法 .reduce() 的可選引數 firstState
- .reduce() 的結果
回撥函式也獲取一個引數 element ,其型別與陣列元素的 型別T 相同,引數 index 是數字,引數 array 是 T值 。
進一步閱讀
- 書 (線上免費閱讀): “Exploring ES6”
- “ECMAScript Language Types” 在 ECMAScript 規範.
- “TypeScript Handbook”: 一本寫的很好解釋了 TypeScript 支援的多種型別及型別操作。
- TypeScript 倉庫裡有完整 ECMAScript 標準庫的型別定義。練習型別標註的簡單方法是閱讀它們。
本文翻譯自原文 - Understanding TypeScript’s type notation -> star
由於本人水平有限,錯誤之處在所難免,敬請指正!
轉載請註明出處,保留原文連結以及作者資訊。