閱讀今天的文章前,先回顧一下昨天的學習,回答看看:
- 通用型別的使用情境為何?
如果有點不清楚答案的話,可以看看 Day17 的文章喔!
TS 的核心原則就是型別檢查,而介面(Interface)正是在 TS 中用來定義抽象物件的資料型別。介面的概念可以想像成是訂定契約,而使用此契約的物件或類別就一定會符合契約中所規定的規範,如果不符合,TS 就會報錯,但是介面只會定義描述有哪些方法(Method)和屬性(Property),無法實作。
在宣告一個介面時,直接使用 interface 關鍵字宣告,介面的命名一般第一個字母
會是大寫,下面是一個基本的介面:
interface Person {
//原始型別
name: string
age: number
//基礎物件型別
birthday: Date
habits: string[]
//函式型別
run(meter: number): void
}
//只有單一函式,可以寫成下面的格式
interface Run{
(meter: number):void
}
介面 Person 內部描述了屬性 name 和 age 以及方法 run 的行為,冒號前面是屬性,後面是型別,但在介面中不會撰寫執行的程式碼。有了介面後,就可以創建宣告一個變數,並要求這個變數遵守介面的規範,舉例如下:
interface Phone {
model: string,
price: number
}
let myPhone: Phone = {
model: 'iphone 11',
price: 40000
}
上面的程式碼範例中,定義了一個介面Phone,規定任何型別為 Phone 的值只能有兩個屬性:string 型別的 model 和 number 型別的 price,而後宣告了一個新的變數 myPhone,型別為 Phone,並遵守介面的規範,將model屬性賦值為字串,price屬性賦值為數字。
倘若多了一個屬性或少了一個屬性都會報錯
//多了一個屬性
let myPhone: Phone = {
model: 'iphone 11',
price: 40000,
screen: '375px'
}
//Error: Type '{ model: string; price: number; screen: string; }' is not assignable to type 'Phone'.Object literal may only specify known properties, and 'screen' does not exist in type 'Phone'.
//少了一個屬性
let myPhone: Phone = {
model: 'iphone 11',
}
//Error: Property 'price' is missing in type '{ model: string; }' but required in type 'Phone'.
// 屬性型別錯誤
let myPhone: Phone = {
model: 'iphone 11',
price: '40000'
}
//Error: Type 'string' is not assignable to type 'number'.
那如果屬性沒有按照順序呢?
let myPhone: Phone = {
price: 40000,
model: 'iphone 11',
}
答案是沒有任何影響,型別檢查器不會檢查屬性的順序,只要相對應的屬性存在且型別對就可以了。
介面只會在編譯時作用,不會編譯輸出成 JS 程式碼
有時候,有些屬性不一定必須,可能是某些條件下存在,這時候就可以使用之前在做型別註記的時候有使用到的可選屬性,在屬性名稱後方加上?
即可
interface Phone {
model: string,
price?: number
}
let myPhone: Phone = {
model: 'iPhone',
}
這時候,price 屬性可有可無,就算沒有這個屬性 TS 也不會報錯。
若希望屬性只能在創建時賦值,賦值後,就再也不能改變其值了。這時候,可以在屬性名稱前面用 readonly 來指定屬性只能在創建值賦值ㄌ:
interface Phone {
readonly model: string,
readonly price: number
}
let myPhone: Phone = { model: 'iphone 11',price: 40000}
myPhone.model = 'ASUS' //Error: Cannot assign to 'model' because it is a read-only property.
readonly v.s. const
readonly的概念很類似常數 const,但兩者的差別就是readonly 是用在屬性上,而const 則使用在變數上。
某些情況下,我們可能只知道介面中部分屬性和其型別,又或者,希望能在初始化後仍然可以增加屬性,這時候就可以使用方式如下:
interface Phone {
model: string,
price?: number,
[x: string]: any
}
let myPhone: Phone = {
model: 'iphone 11',
width: 100
}
上面的程式碼中,使用了 [x: string]定義了任意屬性的型別。要注意的是,一旦定義了任意屬性,那麼確定屬性和可選屬性都必須是它的子屬性。上面的程式碼,任意屬性的型別設定為 any ,所以除了明確訂定型別的 model 屬性外,可以任意添加其他任意型別的屬性。
但倘若任意屬性為「特定」型別,則要注意的是所有屬性(包括確定和可選屬性)都必須是「特定」型別的子屬性
interface Phone {
model: string,
price?: number, // Property 'price' of type 'number' is not assignable to string index type 'string'.
[x: string]: string
}
let myPhone: Phone = {
model: 'iphone 11',
price: 23000,
width: 100, //Type 'number' is not assignable to type 'string'.
}
上面的程式碼中,任意屬性為字串型別,但可選屬性 price 卻是數字型別,數字不是字串的子屬性,因此會報錯。另外,新增加的 width 屬性為數字型別,不符合任意屬性定義的字串型別,因此也會報錯。
今天先初步介紹介面(Interface)宣告的寫法和各種內容屬性,暖身一下,明天會再進一步探討介面的使用情境~
關於「可選屬性(Optional Properties)」
interface Phone {
model: string,
price?: number
}
let myPhone: Phone = {
price: 40000,
}
應該是這樣?
interface Phone {
model: string,
price?: number
}
let myPhone: Phone = {
model: 'i7',
}
還有這裡 @@
interface Phone {
model: string,
price?: number, // Property 'price' of type 'boolean | undefined' is not assignable to string index type 'string'.
[x: string]: string
}
let myPhone: Phone = {
model: 'iphone 11',
price: true,
size: '40000',
number: 0991222222, //Type 'number' is not assignable to type 'string'.
}
price 不是 number
嗎?
謝謝Dylan,寫得太趕案例有誤,已經修正囉