目錄
interface是甚麼
interface是介面,它可以用來定義
物件型別,常以I作為開頭(但實務上以React前端開發來說比較常是...Props)
它關注的重點在於該型別的物件能做什麼
(duck typing)
具體來說有以下幾個常見用法
- 定義class具有的屬性、方法
- 定義function的物件型別參數、React function component的props具有的屬性
- 定義function的參數及回傳值的型別
export interface RoutesProps {
children?: React.ReactNode;
location?: Partial<Location> | string;
}
export function Routes({
children,
location,
}: RoutesProps): React.ReactElement | null {
// 省略
}
export interface NavigateFunction {
(to: To, options?: NavigateOptions): void;
(delta: number): void;
}
export function useNavigate(): NavigateFunction {
// 省略
let navigate: NavigateFunction = React.useCallback(
(to: To | number, options: NavigateOptions = {}) => {
// 省略
},
[basename, navigator, routePathnamesJson, locationPathname]
);
return navigate;
}
定義一般型別
interface IClock {
currentTime: Date,
alarmTime: string,
alarm: () => string | void,
setTime: (time: Date) => void,
setAlarmTime: (time: string) => void,
}
最普通的用法是定義一個物件
const clock = {
currentTime: new Date(),
alarmTime: '13:00',
alarm: () => {
if(`${clock.currentTime.getHours()}:${clock.currentTime.getMinutes()}` !== clock.alarmTime) return;
return 'beep beep beep';
},
setTime: (time:Date) => {
clock.currentTime = time;
},
setAlarmTime: (time:string) => {
clock.alarmTime = time;
},
}
實作interface
同一個interface也能定義class具有的屬性、方法
當用到class時需要用
到implements
來讓class實作interface
class Clock implements IClock{
currentTime: Date;
alarmTime:string;
constructor(time:Date){
this.currentTime = time;
this.alarmTime = '';
}
alarm(){
if(`${this.currentTime.getHours()}:${this.currentTime.getMinutes()}` !== this.alarmTime) return;
return 'beep beep beep';
}
setTime(time: Date){
this.currentTime = time;
}
setAlarmTime(time: string){
this.alarmTime = time;
}
}
定義function型別
enum ComponentName {
FadeCarousel,
SimpleCard,
SearchBar,
InputGroup
}
interface CaseProcessor{
(str: string): string;
}
const snakeCaseConvertor:CaseProcessor = (str) => {
return str.replace(/[A-Z]/g, (d, i) => {
return i < 1? `${d.toLocaleLowerCase()}` : `_${d.toLocaleLowerCase()}`;
});
}
const res = snakeCaseConvertor('SimpleCard');
和泛型一起用
interface CaseProcessor<T, K = T>{
(str: keyof T): K
}
const snakeCaseConvertor:CaseProcessor<typeof ComponentName, Lowercase<keyof typeof ComponentName>> = (str) => {
return str.replace(/_[a-z]+/g, (d, i) => {
return i < 1? `${d.toLocaleLowerCase()}` : `_${d.toLocaleLowerCase()}`;
}) as Lowercase<keyof typeof ComponentName>;
}
const res = snakeCaseConvertor('SimpleCard');
不過眼尖的人可能會發現這邊用Lowercase並不恰當
確實,不過要解決這個問題需要用到infer,之後再開一篇來寫infer吧
定義物件&陣列型別
兩者非常相似,故只用物件當範例
key(或index)必須string(或number)其中一種
,不能為其他型別、number|string
// 定義物件key必須是string
// 定義陣列則index必須是number
interface IStringList {
[key:string]: string
}
const strArr: IStringList = {
Apple: 'apple',
Tomato: 'tomato'
};
有時可能會需要限縮key的範圍
enum Fruit {
Apple = 'apple',
Banana = 'banana',
Tomato = 'tomato'
}
interface IStringList {
[index: keyof typeof Fruit]: string
}
但這會報錯"An index signature parameter type cannot be a literal type or generic type"
因此需要限縮key的範圍
時,可以改用Record
const strArr: Partial<Record<keyof typeof Fruit, string>> = {
Apple: 'apple',
Tomato: 'tomato'
};
延伸interface
interface可以延伸其他interface
,且一次可延伸多個
透過這種方式可以減少重複的部分,以增進彈性和複用性
interface TropicalFruitDemand {
banana:number;
papaya:number;
mango:number;
}
interface TemperatFruitDemand {
apple:number;
cherry:number;
peach:number;
}
interface FruitDemand extends TemperatFruitDemand, TropicalFruitDemand {}
const fruitINeed:FruitDemand = {
apple:1,
cherry:15,
peach:2,
banana:3,
papaya:1,
mango:6,
};
interface 與 type的差異
使用時機
資料會被重複使用
時用interface
// 裝置的資訊 interface DeviceDetail { displayName: string; deviceId: string; browser: string; operatingSystem: string; } // 使用者所登入的裝置 interface UserDeviceInfo { userId: string; devices: DeviceDetail[]; }
定義
靜態格式資料
時用type
創建新名稱
interface會
type不會多個相同名稱
interface可以,而且會自動合併
type不行定義列舉、元組和複合型別
interface只能定義物件型別
,故上列的型別皆無法表現
type都可以延伸
interface可以延伸另一個interface或type
type每次都是宣告一個新的型別
比較表
interface | type | |
---|---|---|
使用時機 | 重複使用 | 靜態格式資料 |
創建新名稱 | 〇 | ✖ |
多個相同名稱 | 〇 | ✖ |
定義列舉、元組和複合型別 | ✖ | 〇 |
延伸(extends) | 〇 | ✖ |
參考資料
TypeScript - Interfaces
TypeScript - Interfaces vs. Intersections
【Day 19】TypeScript 介面(Interface) v.s. 型別別名(Type Alias)
Day 16. 機動藍圖・介面與型別 X 混用與比較 - TypeScript Interface V.S. Type