有時一段程式碼可能會根據它的呼叫方式來處理各種不同的型別。
以下這個例子,identity 函式,含有接收任何可能型別的輸入,並回傳相同的輸入作為輸出:
function identity(input){
return input;
}
identity("abc");
identity(123);
identity({ quote: "aaa" });
可將 input 宣告為 any,而函式回傳型別也可以是 any:
function identity(input: any){
return input;
}
let value = identity(123); // value 的型別為 any
有鑑於 input 是允許任何輸入,需要一種方式來說明 input 型別與函式回傳的型別,兩者之間存在的關係。TypeScript 使用泛型(generics)來抓住型別之間的關係。
通常用單字母名稱來表示,例 T、U 或駝峰式命名,然後使用左右角括號來宣告,例:someFunction<T>
。
泛型函式
可透過將參數型別的別名,放在角括號中,角括號放在參數的小括號之前,就可以將該函式成為泛型。然後這個別名,可用於註記參數型別、回傳型別和函式本體的型別註記。例:
function identity<T>(input: T) {
return input;
}
const numeric = identity("hi"); // 型別:"hi"
const stringy = identity(123); // 型別:123
也可用在箭頭函式:
const identity = <T>(input: T) => input;
identity(123); // 型別:123
替函式加上參數型別,允許函式被不同的輸入重複使用,同時仍然保持型別安全,並避免 any 型別。
明確的泛型呼叫型別
TypeScript 會將無法推斷的引數型別,預設為 unknown 型別。例:
function logWrapper<Input>(callback: (input: Input) => void){
return (input: Input) => {
console.log("Input:", input);
callback(input);
};
}
// 型別:(input: string) => void
logWrapper((input: string) => {
console.log(input.length);
});
// 型別:(input: unknown) => void
logWrapper((input) => {
// 報錯:unknown 型別不存在 length 屬性
console.log(input.length);
});
為了避免預設為 unknown,可使用明確的泛型引數來呼叫函式,該引數型別明確告訴 TypeScript 引數應該是什麼型別。例:
// 型別:(input: string) => void
logWrapper<string>((input) => {
console.log(input.length);
});
多個函式參數型別
以下的 makeTuple 宣告了兩個參數型別,並回傳一個唯讀元組型別的資料,例:
function makeTuple<First, Second>(first: First, second: Second){
return [first, second] as const;
}
// 以下 tuple 的型別:唯讀 [boolean, string]
let tuple = makeTuple(true, "abc");
留意:如果一個函式宣告多個參數型別,則對該函式的呼叫,必須明確宣告任何所有泛型型別。例:
function makePair<Key, Value>(key: Key, value: Value){
return {key, value};
}
// 正確,沒有提供引數型別,其型別為:{key: string; value: number}
makePair("abc", 123);
// 正確,型別為:{key: string; value: number}
makePair<string, number>("abc", 123);
// 正確,型別為:{key: "abc"; value: 123}
makePair<"abc", 123>("abc", 123);
// 報錯:有2個引數型別,但只設定1個
makePair<string>("abc", 123);
泛型介面
介面也可以宣告為泛型。
以下例子中的 Box 宣告,有一個屬性的 T 參數型別。建立一個宣告為帶有型別 Box 引數的物件,並且需強制符合 inside: T 屬性引數型別:
interface Box<T> {
inside: T;
}
let stringyBox: Box<string> = {
inside: abc"
};
let numberBox: Box<number> = {
inside: 123
};
let incorrectBox: Box<number> = {
inside: false
// 報錯:型別 boolean 不可指派給型別 number
};
推斷泛型介面的型別
interface LinkedNode<Value> {
next?: LinkedNode<Value>;
value: Value;
}
function getLast<Value>(node: LinkedNode<Value>): Value {
return node.next ? getLast(node.next) : node.value;
}
// 推斷 value 引數型別:Date
let lastDate = getLast({
value: new Date("09-13-1993")
});
// 推斷 value 引數型別:string
let lastFruit = getLast({
next: {
value: "banana"
},
value: "apple"
});
// 推斷 value 引數型別:number
let lastMismatch = getLast({
next: {
value: 123
},
// 錯誤:型別 boolean 不可指派給型別 number
value: false
});
泛型類別
可宣告任意數量的參數型別,提供給成員使用。例:
// 宣告了 Key 和 Value 兩個參數型別
class Secret<Key, Value> {
key: Key;
value: Value;
constructor(key: Key, value: Value) {
this.key = key;
this.value = value;
}
getValue(key: Key): Value | undefined {
return this.key === key ? this.value : undefined;
}
}
// 型別:Secret<number, string>
const storage = new Secret(123, "abc");
// 型別:string | undefined
storage.getValue(1987);
明確的泛型類別型別
例如上述例子的:
new Secret(123, "abc");
TypeScript 可以推斷出它的型別,然而,如果無法從傳遞給其建構函式的參數推斷出引數型別的話,預設為 unknown。
例:
class CurriedCallback<Input> {
#callback: (input: Input) => void;
constructor(callback: (input: Input) => void) {
this.#callback = (input: Input) => {
console.log("Input:", input);
callback(input);
};
}
call(input: Input) {
this.#callback(input);
}
}
// 型別:CurriedCallback<string>
new CurriedCallback((input: string) => {
console.log(input.length);
});
// 型別:CurriedCallback<unknown>
new CurriedCallback((input) => {
// 報錯:unknown 型別不存在 length 屬性
console.log(input.length);
});
可透過明確的引數型別來避免預設為 uknown,例:
// 型別:CurriedCallback<string>
new CurriedCallback<string>((input) => {
console.log(input.length);
});
繼承(擴充)泛型類別
例:
class Quote<T> {
lines: T;
constructor(lines: T) {
this.lines = lines;
}
}
// SpokenQuote 類別替它的基本類別 Quote<T> 提供 string 陣列作為 T 的引數型別
class SpokenQuote extends Quote<string[]> {
speak() {
console.log(this.lines.join("\n"));
}
}
new Quote("abc").lines; // 型別:string
new Quote([1, 2, 3]).lines; // 型別:number[]
new SpokenQuote(["a", "b"]).lines; // 型別:string[]
// 報錯:型別 number 不可指派給型別 string
new SpokenQuote([1, 2, 3]);
實現泛型介面
例:
// 這個是泛型介面
interface ActingCredit<Role> {
role: Role;
}
class MoviePart implements ActingCredit<string> {
role: string;
speaking: boolean;
constructor(role: string, speaking: boolean) {
this.role = role;
this.speaking = speaking;
}
}
const part = new MoviePart("aaa", true);
part.role; // 型別:string
class IncorrectExtension implements ActingCredit<string> {
role: boolean;
// 以上這行會報錯:型別 boolean 不可指派給型別 string
}
泛型方法
例:
// 類別中宣告了一個泛型型別 Key
class CreatePairFactory<Key> {
key: Key;
constructor(key: Key) {
this.key = key;
}
createPair<Value>(value: Value) {
return {key: this.key, value};
}
}
// 型別:CreatePairFactory<string>
const factory = new CreatePairFactory("role");
// 型別:{key: string, value: number}
const numberPair = factory.createPair(10);
// 型別:{key: string, value: string}
const stringPair = factory.createPair("bbb");
靜態泛型型別
類別當中的靜態成員無權存取任何類別實體的型別資訊。例:
class BothLogger<OnInstance> {
instanceLog(value: OnInstance) {
console.log(value);
return value;
}
// 靜態 staticLog 方法
static staticLog<OnStatic>(value: OnStatic) {
// 報錯:靜態成員不得參考類別引數型別
let fromInstance: OnInstance;
console.log(value);
return value;
}
}
const logger = new BothLogger<number[]>;
logger.instanceLog([1, 2, 3]); // 型別:number[]
// 推斷 OnStatic 引數型別:boolean[]
BothLogger.staticLog([false, true]);
// 明確顯示 OnStatic 引數型別:string
BothLogger.staticLog<string>("hi");
泛型型別別名
TypeScript 當中,每個型別別名可以被給予任意數量的參數型別,例如以下的 Nullish 型別接收一個泛型 T:
type Nullish<T> = T | null | undefined;
泛型型別別名通常與函式搭配使用,用來描述泛型函式的型別:
type CreatesValue<Input, Output> = (input: Input) => Output;
// 型別:(input: string) => number
let creator: CreatesValue<string, number>;
creator = text => text.length; // 正確
// 報錯:型別 string 不可指派給型別 number
creator = text => text.toUpperCase();
泛型可辨識的聯集
例:
type Result<Data> = FailureResult | SuccessfulResult<Data>;
interface FailureResult {
error: Error;
succeeded: false;
}
interface SuccessfulResult<Data> {
data: Data;
succeeded: true;
}
function handleResult(result: Result<string>) {
if(result.succeeded){
// 進到這裡,result 的型別: SuccessfulResult<string>
console.log("hihi" + result.data);
}else{
// 進到這裡,result 的型別: FailureResult
console.error("err" + result.error);
}
// 報錯:型別 Result<string> 沒有屬性 data。
// 型別 FailureResult 沒有屬性 data。
result.data;
}
泛型修飾符號
泛型預設值
給泛型一個預設值。例:
// Quote 接受一個 T 型別的參數,其型別預設為 string
interface Quote<T = string> {
value: T;
}
// explicit 變數將 T 明確設定為 number
let explicit: Quote<number> = {
value: 123
};
// implicit 變數,其 T 型別預設為 string
let implicit: Quote = {
value: "aaa"
};
let mismatch: Quote = {
// 報錯:型別 number 不可指派給型別 string
value: 123
};
需留意的是,所有預設參數型別必須放在宣告清單中的最後位置。
受限制的泛型型別
限制參數型別的語法,是將 extends 關鍵字放在參數型別的名稱之後,然後是限制它的型別。
例:
interface WithLength {
length: number;
}
function logWithLength<T extends WithLength>(input: T){
console.log("Length: " + input.length);
return input;
}
logWithLength("abc"); // 型別:string
logWithLength([false, true]); // 型別:boolean[]
logWithLength({ length: 123 }) // 型別:{length: number}
logWithLength(new Date()); // 報錯:缺少 length 屬性
keyof 和限制參數型別
例:
function get<T>(container: T, key: keyof T) {
return container[key];
}
const roles = {
favofite: "Fargo",
others: ["a", "b", "c"]
};
const found = get(roles, "favorite"); // 型別:string | string[]
發佈留言
很抱歉,必須登入網站才能發佈留言。