interface & 與type的比較


Posted by TempuraEngineer on 2023-01-15

目錄


interface是甚麼

interface是介面,它可以用來定義物件型別,常以I作為開頭(但實務上以React前端開發來說比較常是...Props)

它關注的重點在於該型別的物件能做什麼(duck typing)

具體來說有以下幾個常見用法

  1. 定義class具有的屬性、方法
  2. 定義function的物件型別參數、React function component的props具有的屬性
  3. 定義function的參數及回傳值的型別
export interface RoutesProps {
  children?: React.ReactNode;
  location?: Partial<Location> | string;
}

export function Routes({
  children,
  location,
}: RoutesProps): React.ReactElement | null {
  // 省略
}

react-router - component.tsx

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;
}

react-router - hooks.tsx


定義一般型別

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


#TypeScript #interface #type #介面







Related Posts

AWS Solutions Architect - Associate (SAA) 學習計畫與備考心得: Module 9

AWS Solutions Architect - Associate (SAA) 學習計畫與備考心得: Module 9

There's no hierarchy of pain

There's no hierarchy of pain

Day 81

Day 81


Comments