前言
在面向對象語言中,接口是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類去實現。
TypeScript 中的接口是一個非常靈活的概念,除了可用于 對類的一部分行為進行抽象 以外,也常用于對「對象的形狀(Shape)」進行描述。
?TypeScript 的核心原則之一是對值所具有的結構進行類型檢查,并且只要兩個對象的結構一致,屬性和方法的類型一致,則它們的類型就是一致的。? 在TypeScript里,接口的作用就是為這些類型命名和為代碼或第三方代碼定義契約。
簡單點說,在 TypeScript中,接口是一個很重要的特性,它讓 TypeScript 具備了 JavaScript 所缺少的、描述較為復雜數據結構的能力。
引入主題
其實在 JavaScript 日常開發中,很多時候都需要接口來 “規范” 程序。
假設在 JavaScript 中定義一個函數,用來獲取一個用戶的姓名和年齡的字符串:
function getUserInfo(user) {return `name: ${user.name}, age: ${user.age}`
}
函數調用:
getUserInfo({name: "koala", age: 18})
您可能會問,我們寫 JavaScript 的時候,這個再正常不過了吧?
但請注意,如果這個 getUserInfo()
在多人開發過程中,如果它是個公共函數(多個開發者都會調用),如果不是每個人點進來看函數對應注釋,可能會出現以下錯誤的調用:
// 1: 直接調用,不知道還需要傳參數
getUserInfo() // Uncaught TypeError: Cannot read property 'name' of undefined// 2: 只傳遞一個參數,不知道還有其他參數
console.log(getUserInfo({name: "王佳斌"})) // name: 王佳斌, age: undefined// 3: 參數知道傳遞多少個,但不知鍵名
getUserInfo({name: "王佳斌", width: 560}) // name: 王佳斌, age: undefined// ...
由于 JavaScript 是弱類型的語言,所以 并不會對我們傳入的代碼進行任何的檢測,
😦 有些錯你自己都說不清楚,但是就出了問題。
那么如何解決呢?有請 Typescript 接口登場。
創建接口
指定的接口名稱,最好與普通變量名 “有所區分” ,比如接口名首字母大寫、首字母前綴(In_xxx)等。
在 Typescript 中,使用 interface
關鍵字來定義一個接口,其中 name
就是接口名稱。
interface name {}
基礎使用
Typescript 接口可以規定函數的 “形狀”,也可以規定變量的 “形狀”,下面有兩個示例。
以下 JavaScript 例子(前面已經提到了,忘記的話往前翻):
function getUserInfo(user) {return `name: ${user.name}, age: ${user.age}`
}
這個所存在的問題大家已經知道了,下面用 Typescript 接口進行函數重構。
// 規定"形狀"
interface Info {name: string;age: number;
}// 函數(冒號后跟上 "接口名")
function getUserInfo({ name, age }: Info) {return `name: ${name}, age: ${age}`
}// 正常都傳遞
console.log(getUserInfo({ name: '王佳斌', age: 123 }))
// 結果OK:"name: 王佳斌, age: 123" // 少傳遞一個
console.log(getUserInfo({ name: '王佳斌' }))
// Property 'age' is missing in type '{ name: string; }' but required in type 'Info'.
// 類型“{name:string;}”中缺少屬性“age”,但類型“Info”中需要該屬性。// 都不傳遞
console.log(getUserInfo())
// Expected 1 arguments, but got 0.
// 應為1個參數,但得到了0個。
你看,這些都是在編寫代碼時 TypeScript 提示的錯誤信息,這樣就避免了在使用函數的時候傳入不正確的參數。
注意:在定義接口時,不要把它理解為是在定義一個對象,{}
括號包裹的是一個代碼塊,里面是聲明語句,只不過聲明的不是變量的值而是類型。聲明也不用等號賦值,而是冒號指定類型。每條聲明之前用換行分隔即可,也可以使用分號或者逗號。
另外,接口還可以被變量所使用(繼承接口的 “形狀”),如下代碼所示:
// 規定"形狀"
interface Info {name: string;age: number;
}// 變量 "繼承" 接口
const student: Info = {name: '小王',age: 15
}// 測試變量
console.log(student)//{"name": "小王", "age": 15}// 錯誤用法(比如寫一個 "Info" 接口不存在的參數)
const err: Info = {a: 1
}
// Object literal may only specify known properties, and 'a' does not exist in type 'Info'.
// 對象文字只能指定已知的財產,類型“Info”中不存在“a”。
可選屬性
當然,TypeScript 中也允許不 “必傳” 某些參數,有這個字段就做處理,沒有就忽略。
如下代碼所示,message
參數可以不傳遞。
// 使用 "?" 表示此參數非必傳
interface Log {message?: string;
}// 函數
function print({ message }: Log) {console.log(message || '該參數沒有傳遞~')
}// 傳遞參數
print({ message: 'hello' }) //"hello" // 不傳遞
print({}) //"該參數沒有傳遞~"
很好理解。
只讀屬性
TypeScript 支持將某些參數設置為 “只讀”,用于限制只能在對象剛剛創建的時候修改其值,后續無法再修改。
如下代碼所示,age
參數不可后期修改。
// 使用 "readonly" 關鍵字表示此參數"只讀"
interface Info {name: string;readonly age: number;
}// 創建變量("age"只能初始的時候賦值一次)
const student: Info = {name: '小王',age: 15
}// 測試修改只讀屬性 "age"
student.age = 50
// Cannot assign to 'age' because it is a read-only property.
// 無法分配給“age”,因為它是只讀屬性。
此外 TypeScript 還提供了 ReadonlyArray<T>
類型,它與 Array<T>
相似,只是把所有可變方法去掉了,因此可以確保數組創建后再也不能被修改。
// 創建一個 "絕對不可修改" 的數組(number類型)
let arr: ReadonlyArray<number> = [1, 2, 3, 4]// 測試賦值
arr[0] = 10
// Index signature in type 'readonly number[]' only permits reading.
// 類型為“只讀數字[]”的索引簽名只允許讀取。// 測試添加數組項
arr.push(5)
// Property 'push' does not exist on type 'readonly number[]'.
// 類型“只讀數字[]”上不存在屬性“push”。// 測試賦值數組長度
arr.length = 99
// Cannot assign to 'length' because it is a read-only property.
// 無法分配給“length”,因為它是只讀屬性。
任意屬性
有時候我們希望一個接口中除了包含必選和可選屬性之外,還允許有其他的任意屬性,這時我們可以使用 索引簽名 的形式來滿足上述要求。
如下代碼所示,除了 name
必傳外,后面你可以隨意傳遞參數。
// 使用 "[propName: string]: any" 支持任意類型
interface Person {name: string;[propName: string]: any;
}// 只傳遞必填,其他參數不要
const a: Person = { name: '小王' }
console.log(a) //{"name": "小王"}// 傳遞必填,其他參數隨意傳遞
const b: Person = { name: '小王', age: 15, sex: '男' }
console.log(b) //{"name": "小王", "age": 15, "sex": "男"}
很好理解。