在談到陷阱之前,我們先介紹最後一個基礎型別。
TypeScript 的陣列算是很特殊的物件,可以支援串接、push 、搜尋、切割等。那我們先開始透過TypeScript 自動推論型別的範例來看一下。看起來應該沒什麼困難的,但魔鬼就藏在細節中。
let qq = []; // any[]
let a = [1, 2, 3]; // number[]
let b = ['1', '2', '3']; // string[]
let c = [1, '2']; // ( number | string )[]
const constD = [1, '2']; // ( number | string )[],並不會自動推論成唯一的 [1, '2'] 哦。
let qq1: [] = []; // any[]
let a1: number[] = [1, 2, 3]; // number[]
let b1: string[] = ['1', '2', '3']; // string[]
let c1: (number | string)[] = [1, '2']; // ( number | string )[]
除非必要,盡量不要用 any 與 (number | string)[] 這種複合型別的陣列:
因為陣列元素多種型別會導致未來在處理的時候還必須特地去判別,常見經驗會保持陣列的元素為同一種,也稱之同質性( homegeneous )。
function twoArray(){
let errorArray = [];
errorArray.push(1);
errorArray.push('1');
return errorArray // (number | string)[]
}
let myArray = twoArray();
myArray.push(true); // Error, boolean 不應該放入 (number | string)[] 型別的
因為這樣的原因,const constD = [1, '2']; 才不會被 TypeScript 給推論成 [1, '2'] 而是 ( number | string )[]
屬於 Array 的子型別(subtype)。因為它是嚴格定義好數量與型別的陣列。
元組( tuple ):必須嚴格定義元素的數量與型別。
let a: [number] = [1];
let errorA1: [number] = ['1']; // Error,型態不對
let errorA2: [number] = [1, 2]; // Error,數量不對
因此元組( tuple )可以比原本 Array 帶來更高安全性。
當然一般的陣列是可變動的( mutable ,代表可以使用 .push 或 .splice 或更新);但 TypeScript 提供一個方式讓 Array 來達到類似的效果,readonly array 唯讀陣列型別。
let a: readonly number[] = [1, 2, 3];
a.push(1); // Error, 禁止使用,應該說不存在 push 之類的方法。
當然宣告這種 readonly 有很多種宣告唯讀陣列與元組方法,以下就參考之前提到的書中範例,
type A = readonly string[];
type B = ReadonlyArray<string>;
type C = ReadonlyArray<string[]>;
type D = readonly [number, string];
type E = Readonly<[number, string]>;
但也會帶來一個缺點,就是不可變的話代表每次想特別修改裡面元素就必須要拷貝一個出來。可以參考 lee Byron 絕妙的 immutable 。
有四種型別用來當作缺乏型別( absence type ):null 、 undefined 、void 與 never
它們在 TypeScript 中的屬於特殊型別,是用來準備表示某個東西的??( absence of something )。undefined 的東西只有 undefined 這個值; null 的唯一東西就是,值 null 。聽起來很模糊吧?先稍微提及一點點差異,或許能稍微暸解一下。
undefined:代表某個東西尚未被定義。
null:代表一個數值的缺席。
它們在 TypeScript 中的屬於特殊型別,但用途相對明確的型別。
void:是沒有明確回傳任何東西的函數。
never 是永遠不會回傳的函數,像是永遠不會停止的函數。
那我們來建立一下他們的範例程式碼吧。
// null
function getNullValue(): null {
return null;
}
const nullValue = getNullValue();
console.log(nullValue); // 輸出: null
// undefined
function getUndefinedValue(): undefined {
return undefined;
}
const undefinedValue = getUndefinedValue();
console.log(undefinedValue); // 輸出: undefined
// void
function logMessage(message: string): void {
console.log(message);
}
logMessage("這是一個訊息。"); // 輸出: 這是一個訊息。
// never
function throwError(message: string): never {
throw new Error(message);
} // 這會拋出一個錯誤: "這是一個錯誤!" throwError("這是一個錯誤!");
function trueWhile(message: string): never {
while (true) {
console.log('1');
}
} // 永遠不停止
那常見陷阱與沒有
型別的基本介紹就到這邊了。
還記得很早之前有提到 unknown 是其他型別的超型別( supertype ),那麼 never 就是其他所有型別的子型別( subtype )。這表示他可以被指定給其他型別,並且盡可能的把他們帶到安全的地方~
另外我們會提及一個額外的東西。
可以做為型別的列舉( enumerate ):key 映射到 value,而且是無序的資料結構,舉個例子就像是
enum Language {
English,
Chinese,
...
}
// 上面有提到映射,所以 TypeScript 自己推論後會變成類似這樣
enum Language {
English = 0,
Chinese = 1,
...
}
依照慣例,enum 的名稱要大寫,元素也要大寫且單數。
那我們該怎麼拿取數值呢?
let myMotherTongue1 = Language.Chinese;
let myMotherTongue2 = Language[1];
當然要自己建立這個映射的關西也可以,這還會牽扯到反向查詢的部分。
enum AreaCode {
English = 1,
Taiwan = 886,
}
let myAreaCode1 = AreaCode.Taiwan;
let myAreaCode2 = AreaCode[886];
當然透過反向查詢我們會變得非常方便,但是方便的東西往往會有陷阱。
enum AreaCode {
English = 1,
Taiwan = 886,
}
let myAreaCode1 = AreaCode.Taiwan;
let myAreaCode2 = AreaCode[2];
console.log(typeof myAreaCode1);
console.log(typeof myAreaCode2);
等等,你怎麼找得出 AreaCode[2],我們應該連搜尋都不可以允許呀。那怎麼避免? const !
const enum AreaCode {
English = 1,
Taiwan = 886,
}
let myAreaCode1 = AreaCode.Taiwan;
let myAreaCode2 = AreaCode[2]; // Error 沒有這東西。
這只是其中一個陷阱,因為 enum 是 TypeScript 可指定性規則( assignability rules )的一個不性的後果,要修正它只能盡量地避免,如只是用 key 或 value 也必須是字串最好。
因為在安全地使用 enum 的路上會有很多陷阱,一個不小心就會忘記而踩到,很多人都會建議我們避開他們。