TypeScript接口與泛型全面精講
一、接口
1. Interface 接口類型
(1) 接口類型的基本使用
如下定義一個接口類型:
/ ** 關(guān)鍵字 接口名稱 */
interface ProgramLanguage {
/** 語言名稱 */
name: string;
/** 使用年限 */
age: () => number;
}
現(xiàn)在我們就可以直接使用 ProgramLanguage 接口來定義參數(shù)的類型了:
function NewStudy(language: ProgramLanguage) { console.log(`ProgramLanguage ${language.name} created ${language.age()} years ago.`); }
我們還可以通過復(fù)用接口類型定義來約束其他邏輯。比如,我們通過如下所示代碼定義了一個類型為 ProgramLanguage 的變量 TypeScript :
let TypeScript: ProgramLanguage;
(2) 可缺省屬性
/** 關(guān)鍵字 接口名稱 */
interface OptionalProgramLanguage {
/** 語言名稱 */
name: string;
/** 使用年限 */
age?: () => number;
}
let OptionalTypeScript: OptionalProgramLanguage = {
name: 'TypeScript'
}; // ok
當屬性被標注為可缺省后,它的類型就變成了顯式指定的類型與 undefined 類型組成的聯(lián)合類型,比如示例中 OptionalTypeScript 的 age 屬性類型就變成了如下所示內(nèi)容:
(() => number) | undefined;
(3) 只讀屬性
interface ReadOnlyProgramLanguage { /** 語言名稱 */ readonly name: string; /** 使用年限 */ readonly age: (() => number) | undefined; } let ReadOnlyTypeScript: ReadOnlyProgramLanguage = { name: 'TypeScript', age: undefined } /** ts(2540)錯誤,name 只讀 */ ReadOnlyTypeScript.name = 'JavaScript';
(4) 定義函數(shù)類型
在以上示例中,你可能會覺得接口類型僅能用來定義對象的類型,但是接口類型還可以用來定義函數(shù)的類型(僅僅是定義函數(shù)的類型,而不包含函數(shù)的實現(xiàn)),具體示例如下:
interface person { name: 'zyj', age: 20 } interface func { (persona: person): void } let printmessage: func = persona => { console.log(`我是${persona.name},我的年齡是${persona.age}歲`)}
我們定義了一個接口類型 func,它有一個函數(shù)類型的匿名成員,函數(shù)參數(shù)類型 person,返回值的類型是 void,通過這樣的格式定義的接口類型又被稱之為可執(zhí)行類型,也就是一個函數(shù)類型。
然后我們聲明了一個 func 類型的變量,并賦給它一個箭頭函數(shù)作為值。根據(jù)上下文類型推斷,賦值操作左側(cè)的 func 類型是可以約束箭頭函數(shù)的類型,所以即便我們沒有顯式指定函數(shù)參數(shù) persona 的類型,TypeScript 也能推斷出它的類型就是 person。
實際上,我們很少使用接口類型來定義函數(shù)的類型,更多使用內(nèi)聯(lián)類型或類型別名配合箭頭函數(shù)語法來定義函數(shù)類型,具體示例如下:
type personType = (persona: person) => void
我們給箭頭函數(shù)類型指定了一個別名 personType,在其他地方就可以直接復(fù)用 personType,而不用重新聲明新的箭頭函數(shù)類型定義。
(5) 索引簽名
索引名稱的類型分為 string 和 number 兩種,通過如下定義的 LanguageRankInterface 和 LanguageYearInterface 兩個接口,我們可以用來描述索引是任意數(shù)字或任意字符串的對象:
interface LanguageRankInterface { [rank: number]: string; } interface LanguageYearInterface { [name: string]: number; } { let LanguageRankMap: LanguageRankInterface = { 1: 'TypeScript', // ok 2: 'JavaScript', // ok 'WrongINdex': '2012' // ts(2322) 不存在的屬性名 }; let LanguageMap: LanguageYearInterface = { TypeScript: 2012, // ok JavaScript: 1995, // ok 1: 1970 // ok }; }
注意:在上述示例中,數(shù)字作為對象索引時,它的類型既可以與數(shù)字兼容,也可以與字符串兼容,這與 JavaScript 的行為一致。因此,使用 0 或 '0' 索引對象時,這兩者等價。
注意:雖然屬性可以與索引簽名進行混用,但是屬性的類型必須是對應(yīng)的數(shù)字索引或字符串索引的類型的子集,否則會出現(xiàn)錯誤提示。
{ interface StringMap { [prop: string]: number; age: number; // ok name: string; // ts(2411) name 屬性的 string 類型不能賦值給字符串索引類型 number } interface NumberMap { [rank: number]: string; 1: string; // ok 0: number; // ts(2412) 0 屬性的 number 類型不能賦值給數(shù)字索引類型 string } interface LanguageRankInterface { name: string; // ok 0: number; // ok [rank: number]: string; [name: string]: number; } }
在上述示例中,因為接口 StringMap 屬性 name 的類型 string 不是它所對應(yīng)的字符串索引(第 3 行定義的 prop: string)類型 number 的子集,所以會提示一個錯誤。同理,因為接口 NumberMap 屬性 0 的類型 number 不是它所對應(yīng)的數(shù)字索引(第 8 行定義的 rank: number)類型 string 的子集,所以也會提示一個錯誤。
2. Type 類型別名
接口類型的一個作用是將內(nèi)聯(lián)類型抽離出來,從而實現(xiàn)類型可復(fù)用。其實,我們也可以使用類型別名接收抽離出來的內(nèi)聯(lián)類型實現(xiàn)復(fù)用。
/** 類型別名 */ { type LanguageType = { /** 以下是接口屬性 */ /** 語言名稱 */ name: string; /** 使用年限 */ age: () => number; } }
針對接口類型無法覆蓋的場景,比如組合類型、交叉類型,我們只能使用類型別名來接收,如下代碼所示:
{ /** 聯(lián)合 */ type MixedType = string | number; /** 交叉 */ type IntersectionType = { id: number; name: string; } & { age: number; name: string }; /** 提取接口屬性類型 */ type AgeType = ProgramLanguage['age']; }
注意:類型別名,誠如其名,即我們僅僅是給類型取了一個新的名字,并不是創(chuàng)建了一個新的類型。
3. one question
如何定義如下所示 age 屬性是數(shù)字類型,而其他不確定的屬性是字符串類型的數(shù)據(jù)結(jié)構(gòu)的對象?
{ age: 1, // 數(shù)字類型 anyProperty: 'str', // 其他不確定的屬性都是字符串類型 ... }
我們肯定要用到兩個接口的聯(lián)合類型及類型縮減,這個問題的核心在于找到一個既是 number 的子類型,這樣 age 類型縮減之后的類型就是 number;同時也是 string 的子類型,這樣才能滿足屬性和 string 索引類型的約束關(guān)系。哪個類型滿足這個條件呢?那只有特殊類型 never。
never 有一個特性是它是所有類型的子類型,自然也是 number 和 string 的子類型,所以答案如下代碼所示:
type UnionInterce = | { age: number; } | ({ age: never; [key: string]: string; }); const O: UnionInterce = { age: 2, string: 'string' };
在上述代碼中,我們在第 3 行定義了 number 類型的 age 屬性,第 6 行定義了 never 類型的 age 屬性,等價于 age 屬性的類型是由 number 和 never 類型組成的聯(lián)合類型,所以我們可以把 number 類型的值(比如說數(shù)字字面量 1)賦予 age 屬性;但是不能把其他任何類型的值(比如說字符串字面量 'string' )賦予 age。
同時,我們在第 5 行~第 8 行定義的接口類型中,還額外定義了 string 類型的字符串索引簽名。因為 never 同時又是 string 類型的子類型,所以 age 屬性的類型和字符串索引簽名類型不沖突。如第 9 行~第 12 行所示,我們可以把一個 age 屬性是 2、string 屬性是 'string' 的對象字面量賦值給 UnionInterce 類型的變量 O。
二、泛型
1. 泛型類型參數(shù)
function reflect<P>(param: P) { return param; }
這里我們可以看到,尖括號中的 P 表示泛型參數(shù)的定義,param 后的 P 表示參數(shù)的類型是泛型 P(即類型受 P 約束)。
const reflectStr = reflect<string>('string'); // str 類型是 string const reflectNum = reflect<number>(1); // num 類型 number
然后在調(diào)用函數(shù)時,我們也通過 <> 語法指定了如下所示的 string、number 類型入?yún)?,相?yīng)地,reflectStr 的類型是 string,reflectNum 的類型是 number。
另外,如果調(diào)用泛型函數(shù)時受泛型約束的參數(shù)有傳值,泛型參數(shù)的入?yún)⒖梢詮膮?shù)的類型中進行推斷,而無須再顯式指定類型(可缺?。虼松线叺氖纠梢院唽憺槿缦率纠?/p>
const reflectStr2 = reflect('string'); // str 類型是 string const reflectNum2 = reflect(1); // num 類型 number
泛型不僅可以約束函數(shù)整個參數(shù)的類型,還可以約束參數(shù)屬性、成員的類型,比如參數(shù)的類型可以是數(shù)組、對象,如下示例:
function reflectArray<P>(param: P[]) { return param; } const reflectArr = reflectArray([1, '1']); // reflectArr 是 (string | number)[]
這里我們約束了 param 的類型是數(shù)組,數(shù)組的元素類型是泛型入?yún)ⅰ?/p>
2. 泛型類
在類的定義中,我們還可以使用泛型用來約束構(gòu)造函數(shù)、屬性、方法的類型,如下代碼所示:
class Memory<S> { store: S; constructor(store: S) { this.store = store; } set(store: S) { this.store = store; } get() { return this.store; } } const numMemory = new Memory<number>(1); // <number> 可缺省 const getNumMemory = numMemory.get(); // 類型是 number numMemory.set(2); // 只能寫入 number 類型 const strMemory = new Memory(''); // 缺省 <string> const getStrMemory = strMemory.get(); // 類型是 string strMemory.set('string'); // 只能寫入 string 類型
3. 泛型類型
將類型入?yún)⒌亩x移動到類型別名或接口名稱后,此時定義的一個接收具體類型入?yún)⒑蠓祷匾粋€新類型的類型就是泛型類型。
type GenericReflectFunction<P> = (param: P) => P; interface IGenericReflectFunction<P> { (param: P): P; } const reflectFn4: GenericReflectFunction<string> = reflect; // 具象化泛型 const reflectFn5: IGenericReflectFunction<number> = reflect; // 具象化泛型 const reflectFn3Return = reflectFn4('string'); // 入?yún)⒑头祷刂刀急仨毷?string 類型 const reflectFn4Return = reflectFn5(1); // 入?yún)⒑头祷刂刀急仨毷?number 類型
在泛型定義中,我們甚至可以使用一些類型操作符進行運算表達,使得泛型可以根據(jù)入?yún)⒌念愋脱苌龈鳟惖念愋?,如下代碼所示:
type StringOrNumberArray<E> = E extends string | number ? E[] : E; type StringArray = StringOrNumberArray<string>; // 類型是 string[] type NumberArray = StringOrNumberArray<number>; // 類型是 number[] type NeverGot = StringOrNumberArray<boolean>; // 類型是 boolean
發(fā)散一下,如果我們給上面這個泛型傳入了一個 string | boolean 聯(lián)合類型作為入?yún)?,將會得到什么類型呢?且看如下所示示例?/p>
type BooleanOrString = string | boolean; type WhatIsThis = StringOrNumberArray<BooleanOrString>; // 好像應(yīng)該是 string | boolean ? type BooleanOrStringGot = BooleanOrString extends string | number ? BooleanOrString[] : BooleanOrString; // string | boolean
如果你使用 VS Code 嘗試了這個示例,并 hover 類型別名 WhatIsThis ,那么你會發(fā)現(xiàn)顯示的類型將是 boolean | string[]。這個就是所謂的分配條件類型:
在條件類型判斷的情況下(比如上邊示例中出現(xiàn)的 extends),如果入?yún)⑹锹?lián)合類型,則會被拆解成一個個獨立的(原子)類型(成員)進行類型運算。
比如上邊示例中的 string | boolean 入?yún)ⅲ缺徊鸾獬?string 和 boolean 這兩個獨立類型,再分別判斷是否是 string | number 類型的子集。因為 string 是子集而 boolean 不是,所以最終我們得到的 WhatIsThis 的類型是 boolean | string[]。
4. 泛型約束
function reflectSpecified<P extends number | string | boolean>(param: P):P { return param; } reflectSpecified('string'); // ok reflectSpecified(1); // ok reflectSpecified(true); // ok reflectSpecified(null); // ts(2345) 'null' 不能賦予類型 'number | string | boolean'
同樣,我們也可以把接口泛型入?yún)⒓s束在特定的范圍內(nèi),如下代碼所示:
interface ReduxModelSpecified<State extends { id: number; name: string }> { state: State } type ComputedReduxModel1 = ReduxModelSpecified<{ id: number; name: string; }>; // ok type ComputedReduxModel2 = ReduxModelSpecified<{ id: number; name: string; age: number; }>; // ok type ComputedReduxModel3 = ReduxModelSpecified<{ id: string; name: number; }>; // ts(2344) type ComputedReduxModel4 = ReduxModelSpecified<{ id: number;}>; // ts(2344)
在上述示例中,ReduxModelSpecified 泛型僅接收 { id: number; name: string } 接口類型的子類型作為入?yún)ⅰ?/p>
到此這篇關(guān)于TypeScript接口與泛型全面精講的文章就介紹到這了,更多相關(guān)TypeScript接口與泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析JSON字符串報錯syntaxError:unexpected?end?of?JsoN?input如何解決
這篇文章主要給大家介紹了關(guān)于解析JSON字符串報錯syntaxError:unexpected?end?of?JsoN?input如何解決的相關(guān)資料,文中通過代碼將解決的辦法介紹的非常詳細,需要的朋友可以參考下2024-05-05小程序如何在不同設(shè)備上自適應(yīng)生成海報的實現(xiàn)方法
這篇文章主要介紹了小程序如何在不同設(shè)備上自適應(yīng)生成海報的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08JavaScript中btoa和atob全局函數(shù)示例詳解
這篇文章主要給大家介紹了關(guān)于JavaScript中btoa和atob全局函數(shù)的相關(guān)資料,atob和btoa是window對象的兩個函數(shù),用來編碼解碼Base64,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-08-08