前言
眾所周知伪冰,在傳統(tǒng)的JavaScript中是沒有接口
的概念的巩踏,所謂的接口
,其實就是描述集合屬性的類型的一個特殊的虛擬結(jié)構(gòu)音榜。這也是開發(fā)一個大型項目所必須的語言特性稚叹,像Java动雹、C#這樣強類型語言槽卫,接口已經(jīng)使用得非常廣泛。于是胰蝠,在TypeScrip中也引入了接口
的概念歼培。
一震蒋、接口的基本使用
基與我們前面介紹的對象的類型的聲明,可以定義一個函數(shù)的參數(shù)是包含特定屬性的對象:
function doSomeThing(params: {name: string}):void {
console.log(params);
}
console.log(doSomeThing({name: '馬松松'}));
// { name: '馬松松' }
我們也可以使用接口
的方式實現(xiàn)上面的例子:
interface person {
name: string
}
function doSomeThing(params: person):void {
console.log(params);
}
console.log(doSomeThing({name: '馬松松'}));
// { name: '馬松松' }
兩者是等效的躲庄,使用接口
的好處是可以將參數(shù)類型的配置抽離到一個單獨的文件查剖,這樣使得項目更容易維護。
二噪窘、接口中使用可選參數(shù)
為了增強接口
的靈活性和延展性笋庄,TypeScript允許定義為接口類型的變量可以選擇性匹配。
interface SquireParams {
width?: number,
height?: number
}
function squireResult(params: SquireParams):any {
let result: any;
if (params.width) {
result = params.width * params.width;
}
if (params.height) {
result = params.height * params.height;
}
if (params.width && params.height) {
result = params.width * params.height;
}
return result;
}
console.log(squireResult({height: 5}));
// 25
console.log(squireResult({width: 5}));
// 25
console.log(squireResult({width: 5,height: 5}));
// 25
當(dāng)然倔监,也可以和必選參數(shù)結(jié)合使用:
interface SquireParams {
width?: number,
height?: number,
label: string
}
function squireResult(params: SquireParams):any {
let result: any;
if (params.width) {
result = params.label + params.width * params.width;
}
if (params.height) {
result = params.label + params.height * params.height;
}
if (params.width && params.height) {
result = params.label + params.width * params.height;
}
return result;
}
console.log(squireResult({label: '計算結(jié)果為:', height: 5}));
// 計算結(jié)果為:25
三直砂、接口中使用 只讀屬性
同時,在JavaScript中浩习,沒有關(guān)鍵字標(biāo)識只讀屬性静暂。我們可以通過Object.defineProperty
屬性設(shè)置攔截,在TypeScript中明確提出了只讀屬性的關(guān)鍵字谱秽。
可以這樣使用:
interface readonlyType {
readonly x: number,
readonly y: number
}
let readonlyObj: readonlyType = {x: 10, y: 10}
readonlyObj.x = 13;
//Cannot assign to 'x' because it is a read-only property
只允許初始化的時候洽蛀,給x
和y
分配number
的值。
對于數(shù)組弯院,TypeScript也提供了ReadonlyArray<T>這樣的泛型只讀數(shù)組,刪除了該命名數(shù)組的操作數(shù)組的所有方法泪掀。
const arr: ReadonlyArray<number> = [1,2,3];
當(dāng)你想往該數(shù)組推入一個數(shù)字時听绳,會引發(fā)錯誤:
arr.push()
// Property 'push' does not exist on type 'readonly number[]'
??對于const
和readonly的使用的場景:
TypeScript的官方推薦是:變量使用const,而屬性使用readonly异赫。
四椅挣、Excess Property Checks
這個是解決原生的JavaScript的行為和TypeScript行為不一致的方案,思考這樣一個例子:
interface SquareConfig {
color ?: string,
width ?: number
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return { color: config.color || "red", area: config.width ? config.width * config.width : 20 };
}
我們定義了一個SquareConfig
接口塔拳,然后作為函數(shù)的入?yún)㈩愋褪笾ぃ缓笪覀冞@樣使用這個函數(shù):
let mySquare = createSquare({ colour: "red", width: 100 });
這里TypeScript會給出錯誤提示類型不匹配,但是按照我們之前說的可選參數(shù)的的例子靠抑,這里的color
并不是必須的量九,因為這里故意將color
拼成了colour
,TypeScript對以字面量方式定義對象的方式進行了特殊的類型檢查處理颂碧,而在原生的JavaScript中是靜默忽略的荠列,為了避免出現(xiàn)這種情況,下面是幾種更好的規(guī)避這種錯誤的方式:
1.使用as 強制推斷類型
let mySquare = createSquare({colour: "red", width: 100} as SquareConfig);
2.不使用字面量的方式
let paramsSquare = {colour: "red", width: 100};
let mySecondSquare = createSquare(paramsSquare);
3.加一個額外的動態(tài)屬性
interface SquareConfig {
color ?: string,
width ?: number,
[propName: string]: any;
}
let myThirdSquare = createSquare({colour: "red", width: 100});
當(dāng)你想用傳字面量的方式傳入?yún)?shù)载城,為了規(guī)避不必要的錯誤肌似,使用上面的幾種方式就行。
五诉瓦、在接口中定義 函數(shù)的參數(shù)類型和返回值類型
1.基本使用:
首先定義一個函數(shù)的接口川队,我們定義了參數(shù)的類型和返回值的類型
interface baseFunc {
(firstName: string, lastName: string): string
}
然后這樣使用這個接口:
let myFunc: baseFunc = function (firstName: string, lastName: string) {
return firstName + lastName;
}
2.函數(shù)的入?yún)⒉恍枰?/p>
let mySecondFunc: baseFunc = function (fName: string, lName: string) {
return fName + lName;
}
3.當(dāng)你指定了函數(shù)簽名的類型 函數(shù)的入?yún)⒑头祷刂狄膊恍枰该黝愋土ο福愋拖到y(tǒng)會自動根據(jù)傳入的參數(shù)推斷類型
let myThirdFunc: baseFunc = function (fName, lName) {
return fName + lName;
}
4.但是如果你沒有指定類型 但是返回了和接口返回類型不一致 類型檢查不會通過
let myLastFunc: baseFunc = function (fName, lName) {
let result = fName + lName;
return 11;
}
六、接口中 定義數(shù)組和對象的索引類型
1.基本使用:
interface objectType {
[index: string]: string;
}
在對象中這樣使用這個接口
let myObj: objectType = {name: '馬松松', age: "18"};
可以看到固额,我們定義的索引是string
眠蚂,屬性值也是string
,所以這樣定義是合理的。
但是如果將age
的屬性定義為number
類型对雪,就不符合我們接口的定義:
let myObj: objectType = {name: '馬松松', age: 18}; // 這樣是不符合接口的定義的
在數(shù)組中需要這樣使用定義接口河狐,數(shù)組的索引都是number
類型的:
interface arrayType {
[index: number]: string;
}
然后,你可以這樣使用這個接口:
let myArr: arrayType = ["馬松松","18"];
2.注意字符串索引和直接指定類型的方式一起使用的時候,字符串索引類型的優(yōu)先級更高,所以直接指明屬性的類型 需要保持和字符串索引一樣.
interface numberDictionary {
[index: string]: number,
length: number,
// name: string // 這里使用string會報錯瑟捣,以為你字符串索引返回的類型是number
name: number, // 這樣是可以的
}
3.那你確實想定義不同類型的屬性 可以這樣做
interface secondNumberDictionary {
[index: string]: number | string,
length: number,
name: string // 這樣是可以的
}
4.也可以結(jié)合 readonly 定義只讀屬性
interface thirdNumberDistionary {
readonly [index: string]: string
}
// 此時當(dāng)你想設(shè)置thirdNumberDistionary的屬性的時候就會報錯
let myThirdNumberDictionary: thirdNumberDistionary = {name: '馬松松'};
// myThirdNumberDictionary.name = "宋志露"; // 不可設(shè)置
七馋艺、類和接口的關(guān)系
其他語言中使用接口做頻繁的操作的就是,用類實現(xiàn)一個接口迈套,從而使得類和接口締結(jié)某種強制的聯(lián)系捐祠。
1.基本使用:
我們首先定義一個日期接口:
interface BaseClock {
currentTime: string
}
使用implements
關(guān)鍵詞締結(jié)類和接口的契約關(guān)系:
class MyClock implements BaseClock {
currentTime: ""
constructor(h: number, m: number) {
}
}
締結(jié)的契約關(guān)系為:MyClock
類中必須有類型為string
的currentTime
變量。
2.也可以締結(jié)類中的方法的契約
先定義接口:
interface SecondBaseClock {
getCurrentTime(t: string): void
}
使用implements
締結(jié)契約:
class MySecondClock implements SecondBaseClock {
getCurrentTime(t: string) {
this.currentTime = t;
}
}
締結(jié)的契約關(guān)系為:MySecondClock
類中需要有一個getCurrentTime
方法桑李,且需要一個類型為string
的入?yún)Ⅴ庵瑳]有返回值。
3.在締結(jié)類和接口的契約關(guān)系時 注意new關(guān)鍵詞
當(dāng)使用new
關(guān)鍵詞實例化類時贵白,TypeScript類型檢查器不會檢查靜態(tài)類的構(gòu)造器方法是否滿足締約率拒,而是在你使用new關(guān)鍵詞的時候判斷是否滿足。
比如我們定義一個構(gòu)造函數(shù)的的接口:
interface C {
new (hour: number, min: number)
}
然后使用implements
締結(jié)契約:
class Clock implements C {
constructor(h: number, m: number) {}
}
我們會得到報錯信息:
Class 'Clock' incorrectly implements interface 'C'.Type 'Clock' provides no match for the signature 'new (hour: number, min: number): any'
我們締結(jié)契約的類實際上是滿足了構(gòu)造函數(shù)的接口的禁荒,但是由于TypeScript類型檢查不會直接檢查類中構(gòu)造函數(shù)是否滿足契約猬膨,所以這里會報錯。
所以正確的使用方式是將締結(jié)契約的類賦值給別的變量呛伴,這樣類型檢查系統(tǒng)就會進行類型檢查:
interface ClockInterface {
tick(): void
}
const Clock: ClockConstructor = class Clock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
}
}
這里注意這樣的區(qū)別就好了勃痴。
八、接口中 使用繼承
1.基本使用
我們首先定義一個Square
接口:
interface Square {
width: number,
height: number
}
然后這樣使用:
let square = {} as Square;
square.width = 100;
square.height = 100;
為了使接口可以更靈活的構(gòu)建更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),這里使用到了extends
關(guān)鍵字:
interface baseSquare {
width: number,
}
interface Square extends baseSquare {
height: number
}
let square = {} as Square;
square.width = 100;
square.height = 100;
2.一個接口可以繼承多個接口
interface baseFirstSquare {
width: number,
}
interface baseSecondSquare {
width: number,
}
然后我們可以同時繼承這樣兩個接口:
class MySquare implements baseFirstSarare,baseSecondSquare {
color: string
}
九热康、接口中使用 混合類型
基于JavaScript語言的豐富性和靈活性沛申,TypeScript允許使用混合類型
比如定義一個定時器接口:
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
然后你這樣使用:
function getCounter(): Counter {
let counter = function (start: number) {} as Counter;
counter.interval = 123;
counter.reset = function () {};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
這里使用as
推斷了類型,就獲取了一個對象姐军,這里個人有點不理解铁材。
九、接口繼承
1.當(dāng)繼承的類是public時奕锌,可以直接實現(xiàn)這個接口
class Control {
public state: any
}
interface SelectableControl extends Control {
select(): void
}
這樣使用:
let select: SelectableControl = {
state: 22,
select() {}
}
2.當(dāng)繼承的類private或者protected時 繼承的接口只能通過被繼承類子類去實現(xiàn)衫贬,不能直接實現(xiàn)
class SecondControl {
private state: any
}
interface SecondSelectableControl extends SecondControl {
select(): void
}
只能是被繼承類的子類去實現(xiàn)該接口,因為只有被繼承類的子類才能訪問私有屬性:
class MySecondSelectableControl extends SecondControl implements SecondSelectableControl {
select() {
}
}
然后你這樣使用:
let s = new MySecondSelectableControl();
總結(jié):接口的使用歇攻,其實也是引進了強類型語言的相關(guān)的概念固惯,理解接口概念的同時,同時也能增強前端開發(fā)者對強類型語言和弱類型語言的特性的理解缴守。