型別別名與介面
型別別名與介面非常相似,使用物件型別別名來描述物件具有 born: number 和 name: string 的語法:
type Poet = {
born: number;
name: string;
};
然而,如果是使用介面,語法是:
interface Poet {
born: number;
name: string;
}
喜歡分號的開發者,通常將分號放在型別別名之後,而不是在介面之後。
然後就可以使用以下語法來設定 valueLater 的型別或介面為 Poet:
let valueLater: Poet;
然而,介面和型別別名之間,有幾個區別:
- 介面可以「合併」,進行擴充。
- 介面可用於類別宣告做型別結構的檢查,而型別別名不能。
- 使用介面,可以讓 TypeScript 型別檢查變得更快速:在內部宣告一個可以更容易暫存的命名型別,而非像型別別名那樣,動態複製和貼上檢查新物件字面資料。
- 因為介面被認為是命名物件,而不是未命名物件的別名,所以它們的錯誤訊息在一些情況下,更易閱讀。
屬性型別
可選擇的屬性
可透過問號,來表示介面的屬性是可選擇的:
interface Book {
author?: string; // author 屬性是可選的
pages: number;
}
// 正確
const ok: Book = {
author: "abc",
pages: 80
};
// 以下報錯:Property 'pages' is missing in type '{ author: string; }' but required in type 'Book'.
const missing: Book = {
author: "def"
};
唯讀屬性
可以在屬性名稱之前,加上 readonly 關鍵字,來表示該屬性是唯讀的,不能重新指派新的資料。
例:
// Page 介面的 text 屬性,可存取然後會回傳字串,但如果指派新資料給 text 的話,會導致型別錯誤
interface Page {
readonly text: string;
}
function read(page: Page) {
console.log(page.text); // 正確
// 報錯:因為 text 是唯讀屬性
page.text += "!";
}
const myPage: Page = {
text: "hi"
};
// 報錯:因為 text 是唯讀屬性
myPage.text += "!";
函式和方法
TypeScript 允許將介面成員,宣告為函式的兩個方式:
- 方法(Method)語法:宣告介面的成員,作為物件成員呼叫的函式,例如 member(): void。
- 屬性(Property)語法:宣告介面的成員,為獨立函式,例如 member: () => void。
例如以下:method 和 property 成員,都是可以不帶參數呼叫,並回傳 string 的函式:
interface HasBothFunctionTypes {
property: () => string;
method(): string;
}
const hasBoth: HasBothFunctionTypes = {
property: () => "",
method() {
return "";
}
};
hasBoth.property(); // 正確
hasBoth.method(); // 正確
也可以使用問號來表示可選擇(optional),例:
interface HasBothFunctionTypes {
property?: () => string;
method?(): string;
}
方法和屬性宣告大多可以互換使用,一個主要的差別是:
- 方法不能宣告為 readonly,但屬性可以。
呼叫特徵
宣告介面和物件型別會具有呼叫特徵(call signatures)。呼叫特徵看起來類似於函式型別,但用冒號取代箭頭,例:
type FunctionAlias = (input: string) => number;
interface CallSignature {
(input: string): number;
}
// 型別:(input: string) => number
const typedFunctionAlias: FunctionAlias = (input) => input.length; // 正確
// 型別:(input: string) => number
const typedCallSignature: CallSignature = (input) => input.length; // 正確
下例,keepsTrackOfCalls 函式宣告中,讓 count 屬性具有 number 型別,使其可指派給 FunctionWithCount 介面:
interface FunctionWithCount {
count: number;
(): void;
}
let hasCallCount: FunctionWithCount;
function keepsTrackOfCalls() {
keepsTrackOfCalls.count += 1;
console.log("已被執行" + keepsTrackOfCalls.count + "次。");
}
keepsTrackOfCalls.count = 0;
hasCallCount = keepsTrackOfCalls; // 正確
function doesNotHaveCount() {
console.log("No idea!");
}
// 報錯:型別 () => void 缺少屬性 count,型別 FunctionWithCount 必須具有該屬性。
hasCallCount = doesNotHaveCount;
索引特徵
TypeScript 提供了一種稱為索引特徵(index signature)的語法,用來表示可以接受任何的字串 key,並回傳該 key 下的特定型別。最常用於字串鍵值。
例如以下寫法:
// WordCounts 是一個可以接受任何字串 key,然後都要回傳 number 型別的一個介面
interface WordCounts {
[i: string]: number;
}
const counts: WordCounts = {};
counts.apple = 0; // 正確
counts.banana = 1; // 正確
// 報錯:型別 boolean 不能指派給型別 number
counts.cherry = false;
索引特徵會有一個問題:無論存取什麼屬性,預設上,物件都會回傳一個資料。例:
interface DatesByName {
[i: string]: Date;
}
const a: DatesByName = {
b: new Date("1 January 1818")
};
a.b; // 型別 Date
console.log(a.b.toString()); // 正確
a.c; // 型別 Date,但執行時為 undefined
console.log(a.c.toString()); // 在型別系統中會是正確的,但執行時期會報錯:無法讀取屬性 toString
混合的屬性和索引特徵
介面能夠包含明確的屬性命名和包羅萬象的 string 索引特徵。有一點要留意:每個命名屬性的型別,必須可以指派給一般的索引特徵型別。所以可以混合,就像告訴 TypeScript 命名屬性制訂出更具體的型別,而任何其它屬性都可以回退到索引特徵的型別。例:
// HistoricalNovels 介面,宣告所有屬性都是 number 型別,但 Oroonoko 屬性必須存在
interface HistoricalNovels {
Oroonoko: number;
[i: string]: number;
}
// 正確
const novels: HistoricalNovels = {
Outlander: 1991,
Oroonoko: 1688
};
// 報錯,型別 {Outlander: number} 缺少屬性 Oroonoko。
const missingOroonoko: HistoricalNovels = {
Outlander: 1991
};
再看以下例子:
// ChapterStarts 介面,宣告 preface 屬性必須為 0,而其它屬性為 number 型別。
interface ChapterStarts {
preface: 0;
[i: string]: number;
}
const a: ChapterStarts = {
preface: 0,
night: 1,
shopping: 5
};
// 以下報錯:型別 1 不可指派給型別 0。
const b: ChapterStarts = {
preface: 1
};
數字索引特徵
JavaScript 會預設將 key 轉換為字串。然而,也可以使用數值當作物件的 key。然而在 TypeScript 中,該如何撰寫呢:
// 允許,因為 string 可指派給 string | undefined。
interface MoreNarrowNumbers {
[i: number]: string;
[i: string]: string | undefined;
}
// 正確
const mixesNumbersAndStrings: MoreNarrowNumbers = {
0: "",
key1: "",
key2: undefined
};
// 報錯:number 索引型別 string | undefined 無法指派給 string 索引型別 string。
interface abc {
[i: number]: string | undefined;
[i: string]: string;
}
巢狀介面
介面也可以寫成巢狀的形式。例:
interface Novel {
author: {
name: string;
};
setting: Setting;
}
interface Setting {
place: string;
year: number;
}
let myNovel: Novel;
// 正確
myNovel = {
author: {
name: "aaa"
},
setting: {
place: "bbb",
year: 1812
}
};
let myNovel2: Novel;
myNovel2 = {
author: {
name: "aaa"
},
setting: {
// 錯誤:型別 {place: string} 缺少屬性 year
place: "bbb"
}
};
介面繼承
一個介面,可以包含另一個介面的所有相同成員,並且還會加入一些額外的功能。
TypeScript 允許一個介面 繼承或稱做擴充(extend) 另一個介面,會複製另一個介面的所有成員。
語法及範例:
interface Writing {
title: string;
}
// Novella 介面繼承 Writing 介面。
// Novella 稱做衍生介面;Writing 稱做基本介面。
interface Novella extends Writing {
pages: number;
}
// 正確
let myNovella: Novella = {
pages: 195,
title: "aaa"
};
// 報錯:少了 pages 屬性
let abc: Novella = {
title: "aaa"
};
// 報錯:只可以指定已知的屬性,所以不能有 author 屬性
let def: Novella = {
pages: 195,
title: "aaa",
author: "John"
};
覆寫(override)的屬性
TypeScript 的型別檢查,將強制被覆寫的屬性,必須可以指派給它的基本屬性,這樣做是為了確保衍生介面型別的實作,可保持指派給基本介面型別。
例:
interface WithNullableName {
name: string | null;
}
// 正確
interface WithNonNullableName extends WithNullableName {
name: string;
}
// 會報錯:屬性 name 的型別不相容
interface WithNumericName extends WithNullableName {
name: number | string;
}
擴充(繼承)多個介面
在衍生介面名稱之後的 extends 關鍵字,可以使用多個數量的介面名稱,用逗號分隔即可。
例:
interface a {
giveA(): number;
}
interface b {
giveB(): string;
}
interface c extends a, b {
giveC(): number | string;
}
function my_func(instance: c) {
instance.giveC(); // 型別 number | string
instance.giveA(); // 型別 number
instance.giveB(); // 型別 string
}
介面合併
如果兩個介面以相同的名稱宣告在同一個 scope 當中,它們將會自動合併。(建議少用此方式)
例:
interface Merged {
fromFirst: string;
}
interface Merged {
fromSecond: number;
}
// 以上等同於:
// interface Merged {
// fromFirst: string;
// fromSecond: number;
// }
成員命名上的衝突
例:
interface MergedProperties {
same: (input: boolean) => string;
different: (input: string) => string;
}
interface MergedProperties {
same: (input: boolean) => string; // 正確
// 以下會報錯,因為跟前面的衝到了。
different: (input: number) => string;
}
合併的介面可以定義具有相同名稱和不同特徵的方法。這樣做會為該方法建立一個函式重載(overload)。
例:
interface MergedMethods {
different(input: string): string;
}
interface MergedMethods {
different(input: number): string; // 正確
}
發佈留言
很抱歉,必須登入網站才能發佈留言。