TypeScript 的核心就是對(duì)值所具有的結(jié)構(gòu)進(jìn)行類型檢查。接口規(guī)定了行為和規(guī)范长搀,起到限制和規(guī)范的作用宇弛。
接口使用 interface
關(guān)鍵字聲明。一般首字母大寫源请,根據(jù) ts 命名規(guī)范枪芒,接口名加前綴 I
。
本文包括對(duì)象類型谁尸、函數(shù)類型舅踪、可索引的類型、混合類型的接口以及繼承接口良蛮。
對(duì)象類型的接口
在函數(shù)調(diào)用時(shí)對(duì)傳入的對(duì)象屬性進(jìn)行檢查限制抽碌,可以通過(guò)以下方式:
function getFoodInfo({ name, color }: { name: string; color: string }): string {
return `the ${name} color is ${color}!`
}
通過(guò)接口來(lái)描述上例:
interface IFood {
name: string
color: string
}
function getFoodInfo({ name, color }: IFood): string {
return `the ${name} color is ${color}!`
}
getFoodInfo({
name: 'cake',
color: 'white'
})
類型檢查器不會(huì)檢查屬性的順序,只要相應(yīng)的屬性存在并且類型一致即可决瞳。
可選屬性
接口中的屬性并不都是必需的货徙,有些屬性在某些特定條件下存在。
帶有可選屬性的接口只是在可選屬性名字定義的后面加一個(gè) ?
符號(hào)皮胡。
interface AreaConfig {
name: string
width?: number
}
// width 屬性為可選的
function getAreaInfo({ name, width }: AreaConfig): {
name: string
square: number
} {
const area = { square: 9, name }
if (width) area.square = width * width
return area
}
getAreaInfo({ name: 'beijing' })
可選屬性的好處:
- 可以對(duì)可能存在的屬性進(jìn)行預(yù)定義
- 提前捕獲引用了不存在的屬性時(shí)拋出的錯(cuò)誤
只讀屬性
有些對(duì)象的屬性在對(duì)象創(chuàng)建之后是不可修改的痴颊。可以在屬性名前用 readonly
來(lái)指定只讀屬性:
對(duì)象屬性
interface Food {
readonly name: string
color: string
}
const tomato: Food = {
name: 'tomato',
color: 'red'
}
tomato.name = 'potato' // Cannot assign to 'name' because it is a read-only property.
不可變數(shù)組
ReadonlyArray<T>
可以創(chuàng)建一個(gè)不可變的數(shù)組胸囱,ReadonlyArray<T>
類型與 Array<T>
相似祷舀,只是把所有可變方法去掉了,因此可以確保數(shù)組創(chuàng)建后再也不能被修改:
定義一個(gè)普通數(shù)組:
let arr: Array<number> = [1, 2, 3]
arr[1] = 4
console.log(arr) // [1, 4, 3]
創(chuàng)建一個(gè)不可改變的數(shù)組:
let array: ReadonlyArray<number> = [5, 6, 7, 8]
不可變數(shù)組的特性:
- 不可修改某一項(xiàng):
array[1] = 55
// Index signature in type 'readonly number[]' only permits reading.
- 不可刪除、添加元素
array.push(7)
array.splice(0, 1)
// Property 'splice' does not exist on type 'readonly number[]'.
- 不可修改數(shù)組 length 屬性
array.length = 6
// ? Cannot assign to 'length' because it is a read-only property.
- 不可以把 ReadonlyArray 數(shù)組賦值給一個(gè)普通數(shù)組
arr = array
// ? The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
用類型斷言可以進(jìn)行賦值:
arr = array as number[]
arr[1] = 555
console.log(array === arr) // true
// ? array 和 arr 指向同一個(gè)引用 裳扯,并且可以通過(guò)arr去更改array的元素抛丽,但是仍然不可以直接通過(guò)array修改數(shù)組的元素
array[1] = 111 // Index signature in type 'readonly number[]' only permits reading.
注意:readonly 聲明的只讀數(shù)組類型與 ReadonlyArray 聲明的只讀數(shù)組類型,二者等價(jià)饰豺。
const arr1: readonly number[] = [1, 2]
const arr2: ReadonlyArray<number> = [1, 2, 3]
readonly
vs const
的區(qū)別
const
定義的變量不可重新賦值亿鲜,但是其屬性值是可改變的,而 readonly
定義的屬性不可改變冤吨。
類型兼容
TypeScript 的核心原則之一是對(duì)值所具有的結(jié)構(gòu)進(jìn)行類型檢查蒿柳。 它有時(shí)被稱做鴨式辨型法或結(jié)構(gòu)性子類型化。
鴨式辨型來(lái)自于 James Whitecomb Riley 的名言:"像鴨子一樣走路并且嘎嘎叫的就叫鴨子漩蟆。" 通過(guò)制定規(guī)則來(lái)判定對(duì)象是否實(shí)現(xiàn)這個(gè)接口垒探。
看以下例子:
const info = { age: 3, name: 'jack' }
function getInfo(info: { name: string }): string {
return info.name
}
getInfo(info) // ?
getInfo({ name: 'jack', age: 3 }) // ?
通過(guò)接口約束:
interface Info {
name: string
}
function getInfo(info: Info): string {
return info.name
}
getInfo(info) // ?
getInfo({ name: 'jack', age: 3 }) // ?
為什么賦值會(huì)使得類型檢測(cè)變得不嚴(yán)格呢?
因?yàn)樵趨?shù)中直接賦值怠李,對(duì)象會(huì)有嚴(yán)格的類型檢查圾叼。而將對(duì)象賦值給變量 info,該變量會(huì)推斷為 info: {name: string; age: number;} = {name: 'jack', age: 3 }
捺癞,然后再將變量賦值給參數(shù)夷蚊,參照鴨式辨型法,兩種類型的對(duì)象都具有 name 屬性髓介,所以被認(rèn)為是相同的惕鼓,進(jìn)而可以繞開多余屬性的檢查。
額外屬性檢查
所謂額外屬性是指對(duì)象中的屬性沒有在接口中定義過(guò)唐础。
比如:
interface Food {
name: string
size: number
}
function getFood(info: Food): string {
return `${info.name} size is ${info.size}, color is ${info.color}`
}
getFood({ name: 'tomato', size: 10, color: 'red' }) // ?
// Argument of type '{ name: string; size: number; color: string; }' is not assignable to parameter of type 'Food'.
// Object literal may only specify known properties, but 'color' does not exist in type 'Food'.
在調(diào)用 getFood 時(shí) 傳入的參數(shù)多了一個(gè) color 屬性箱歧,而接口中沒有定義 color,ts 類型檢查器檢測(cè)出 color 這個(gè)額外屬性從而會(huì)拋出錯(cuò)誤彻犁。
繞開 ts 對(duì)額外屬性的檢查有三種方式:
1. 類型斷言(最簡(jiǎn)便的方式)
getFood({ name: 'tomato', size: 10, color: 'red' } as Food)
2. 字符串索引簽名(最佳方式)
interface Food {
name: string
size: number
[key: string]: any
}
3. 類型兼容(將對(duì)象賦值給另一個(gè)變量)
const obj = { name: 'tomato', size: 10, color: 'red' }
getFood(obj)
可索引的類型
可索引的類型描述的是可以通過(guò)索引得到的類型叫胁。比如 arr[1] 或 person.name。說(shuō)白了就是數(shù)組或?qū)ο笞裱囊?guī)范和約束汞幢。可索引類型具有一個(gè) 索引簽名微谓,描述了索引的類型以及值的類型森篷。
比如下例用接口約束數(shù)組:StringArray 接口的索引簽名類型為 number,索引簽名值的類型為 string
interface StringArray {
[index: number]: string
}
const arr: StringArray = ['red', 'pink']
索引簽名有兩種形式:數(shù)字和字符串
1. 數(shù)字:
interface Arr {
[index: number]: number
}
const arr: Arr = [1, 2, 3]
2. 字符串:
interface PropString {
[prop: string]: number
}
const objStr: PropString = {
success: 1,
failed: 0
}
將索引簽名設(shè)置為只讀豺型,可以防止給索引賦值:
interface Sina {
readonly [index: number]: string
}
const sian: Sina = {
1: 'sina'
}
sian[1] = 'xiaomi' // ? Index signature in type 'Sina' only permits reading
注意:一旦定義了可索引簽名仲智,則必選屬性和可選屬性的值的類型都必須是可索引類型值的子類型,因?yàn)楸剡x屬性與可選屬性也索引屬性的一種:
interface Person {
name: boolean // ?
age: number // ?
sex?: string // ?
skill: undefined // ?
[key: string]: string
}
可同時(shí)使用兩種類型的索引姻氨,但是數(shù)字類型的索引對(duì)應(yīng)的值必須是字符串類型索引對(duì)應(yīng)的值的子類型钓辆,因?yàn)楫?dāng)使用數(shù)字作為索引時(shí),javascript 會(huì)將數(shù)字轉(zhuǎn)成字符串再去索引對(duì)象
如下例:string 類型屬性的返回值為 string 類型,所以 number 類型屬性的返回值必須為 string 的子類型(包括 string)
// ?
interface PropType {
[id: number]: string
[propName: string]: string
}
// ?
interface PropType {
[id: number]: number
[propName: string]: any
}
// ? Numeric index type 'number' is not assignable to string index type 'string'.
interface PropType {
[id: number]: number
[propName: string]: string
}
當(dāng)索引屬性使用聯(lián)合類型且接口中存在可選屬性時(shí)前联,需要聯(lián)合 undefined
類型功戚,否則編譯報(bào)錯(cuò),因?yàn)榭蛇x屬性是額外屬性的字類型:
interface Info {
name: string
age?: number // age的類型實(shí)際為:number | undefined
[key: string]: string | number | undefined
}
const info: Info = {
name: 'jack',
age: 2
}
// ?
函數(shù)類型
接口除了可以描述帶有屬性的普通對(duì)象外似嗤,還可以描述函數(shù)類型啸臀。
使用接口表示函數(shù)類型時(shí),需要給接口定義一個(gè)調(diào)用簽名烁落,用來(lái)描述函數(shù)的參數(shù)和返回值的類型乘粒。如下:
interface FuncInterface {
(num1: number, num2: number): number
}
const add: FuncInterface = (n1, n2) => n1 + n2
add(2, 5)
參數(shù)名不需要與接口里定義的參數(shù)名相同。此外由于 TypeScript 的類型系統(tǒng)會(huì)推斷出參數(shù)類型伤塌,所以參數(shù)類型可以不指定灯萍。函數(shù)的返回值類型會(huì)通過(guò)返回值推斷出來(lái),也可以不指定每聪。
繼承接口
接口可以通過(guò) extends
關(guān)鍵字實(shí)現(xiàn)繼承旦棉。
interface BaseInfo {
name: string
age: number
}
interface AddressInfo extends BaseInfo {
address: string
}
const userInfo: AddressInfo = {
name: 'jack',
age: 12,
address: 'shanghai'
}
繼承多個(gè)接口:
interface AllInfo extends BaseInfo, AddressInfo {
hobby: string
}
const allInfo: AllInfo = {
name: 'mike',
age: 12,
address: 'beijing',
hobby: 'song'
}
混合類型
函數(shù)也是對(duì)象,也有自己的屬性和方法熊痴。
通過(guò) TypeScript 混合類型接口定義他爸。
interface Counter {
// 函數(shù)參數(shù)和返回值類型
(name: string): void
// 函數(shù)屬性及類型
count: number
// 函數(shù)方法及返回值
rest(): void
}
// 定義一個(gè)函數(shù),該函數(shù)返回Counter類型的函數(shù)
function getCounter(): Counter {
const getCount = (name: string) => {
getCount.count++
}
getCount.count = 0
getCount.rest = function () {
this.count = 0
}
return getCount
}
const getCount = getCounter()
getCount() // 1
getCount() // 2
getCount.rest() // 0