巧用 TypeScript(三)

三毛丶發表於2018-11-18

陣列與元組

建立一個陣列很簡單:

const arr = [1];
複製程式碼

此時 TypeScript 將會推斷 arr 型別為 number[]

arr.push('1');  // Error
複製程式碼

當陣列元素具有其它型別時,可以通過型別註解的方式:

const arr: Array<string | number> = [1];

arr.push('1');  // OK
arr.push(true);  // Error
複製程式碼

或者你也可以通過可選元組的方式:

const arr: [number, string?] = [1];         // arr 的成員型別可以是: number, string, undefined 
arr.push('1');   // OK
arr.push(true);   // Error
複製程式碼

使用元組形式,還能提供指定位置的型別檢查:

arr[0] = '1';   // Error
arr[1] = 1;    // Error
複製程式碼

使用

通常,我們使用 Promise.all 並行發出多個請求:

interface A {
  name: string;
}

interface B {
  age: number;
}

const [{ data: a }, { data: b }] = await Promise.all([
  axios.get<A>('http://some.1'),
  axios.get<B>('http://some.2')
])
複製程式碼

此時,TypeScript 能推出 a 的型別是 A, b 的型別是 B

現在,稍作改變:當滿足特定條件時,才發出第二個請求:

// 使用型別註解
const requestList: [Promise<AxiosResponse<A>>, Promise<AxiosResponse<B>>?]
                    = [axios.get<A>('http://some.1')];
if (flag) {
  requestList[1] = (axios.get<B>('http://some.2'));
};
const [ { data: a }, response ] = await Promise.all(requestList);
複製程式碼

我們期望它會如預想時那樣工作,可是事與願違,Promise.all(requestList),會出現型別相容性的報錯,在這個 Issues 裡,描述了相同的問題。

現在,你可以通過斷言的方式,來讓程式正常運作:

const requestList: any[]  = [axios.get<A>('http://some.1')];  // 設定為 any[] 型別
if (flag) {
  requestList[1] = (axios.get<B>('http://some.2'));
}
const [
  { data: a },
  response
] = await Promise.all(requestList) as [AxiosResponse<A>, AxiosResponse<B>?] // 型別安全
複製程式碼

字面量型別

在 JavaScript 基礎上,TypeScript 擴充套件了一系列字面量型別,用來確保型別的準確性。

如建立一個字串字面量:

const a = 'hello';  // a 的型別是 'hello'
a = 'world';   // Error
複製程式碼

或者你也可以:

let a: 'hello' = 'hello';  // a 的型別是 'hello'
a = 'world';     // Error
複製程式碼

其它資料型別與此相似。

你也可以定義交叉型別與聯合型別的字面量:

interface A {
  name: string;
}
interface B {
  name: string;
  age: number;
}

type C = A | B;
type D = A & B;
複製程式碼

物件字面量型別

對於物件字面量的型別,TypeScript 有一個被稱之為 「Freshness 」的概念,它也被稱為更嚴格的物件字面量檢查,如下例子:

let someThing: { name: string };
someThing = { name: 'hello' };              // ok
someThing = { name: 'hello', age: 123 };    // Error, 物件字面量只能指定已知屬性, { name: string } 型別中不存在 age 屬性

let otherThing = { name: 'hello', age: 123 };
someThing = otherThing;                     // ok
複製程式碼

TypeScript 認為建立的每個物件字面量都是 「 fresh 」 狀態;當一個 「 fresh 」 物件字面量賦值給一個變數時,如果物件的型別與變數型別不相容時,會出現報錯(如上例子中 someThine = { name: 'hello', age: 123 }; 的錯誤);當物件字面量的型別變寬,物件字面量的 「 fresh 」 狀態會消失(如上例子中 someThing = otherThing; ,賦值以後,someThing 的型別變寬)。

一個更實際的用例如下:

function logName(something: { name: string }) {
  console.log(something.name);
}

const obj = {
  name: 'matt',
  job: 'being awesome'
}

logName(obj); // ok
logName({ name: 'matt' }); // ok
logName({ nama: 'matt' }); // Error: nama 屬性在 { name: string } 屬性中不存在。
logName({ name: 'matt', job: 'being awesome' }); // Error: 物件字面量只能指定已知屬性,`job` 屬性在這裡並不存在。
複製程式碼

基本原理與上文中相似,當想用更嚴格的型別檢查時,可以傳一個具有 fresh 狀態的物件字面量(如 logName({ name: 'matt', job: 'being awesome' });)。當你想多傳一些屬性至函式,可以將物件字面量賦值至一個新變數,然後再傳至函式(如 logName(obj))。或者你也可以通過給函式形參新增多餘型別的方式 function logName(someThing: { name: string; [key: string]: string })

更多

相關文章