React(16) - Generic function component


Posted by TempuraEngineer on 2023-05-27

目錄


Generic component是什麼

純Typescript有泛型的功能,用Typescript寫的Reactcomponent也可以和泛型結合

這個技巧相當地實用,在一些UI library內也會看到


泛型的React component幾個優點

  1. 利於複用
  2. 維持型別彈性同時也避免過於彈性,故能避開潛在的型別錯誤


Generic component怎麼寫

假設有個組件叫GenericSelect,它是一個包裝select而成的組件

其選項會有多個,選項的文字一定是string,值則可能是string或者number

當onChange被處發時,它的上層組件可以透過onSelect拿到選擇的值

開始前可以先想一下組件可能怎麼被使用

      <GeniricSelect
        options={genderOptions}
        onSelect={(selectedGender) => {
          // 直接接收值
          setGender(selectedGender);
        }}
      />

      <GeniricSelect
        options={genderOptions}
        onSelect={({ target }) => { 
          // 接收event,再取出target.value
          setGender(target.value);
        }}
      />

那麼先來定義props

  interface GeniricSelectProps<T> {
    options: { 
      text: string; 
      // 值可能是string或number,所以用泛型
      // 至於為什麼不寫string | number,因為這樣型別過於廣泛,所以用泛型來限縮範圍
      value: T 
    }[]; 
    onSelect: (value: T) => void;
  }

然後寫組件本體

這時會發現有type error,這是因為e.target.value會被預設認為是string即便input type="number"也一樣

這時有兩個方式

1.一種是直接把value的型別改為unknown,然後在使用GenericSelect的地方,傳function給onSelect時,內部加上type guard

interface GeniricSelectProps<T> {
  options: { text: string; value: T }[];
  onSelect: (value: unknown) => void;
}

const GeniricSelect = <P extends string | number>({
  options,
  onSelect
}: GeniricSelectProps<P>) => {
  return (
    <select
      name=""
      id=""
      style={{ padding: 8 }}
      onChange={(e: ChangeEvent<HTMLSelectElement>) => onSelect(e.target.value)}
    >
      {options?.map(({ text, value }) => (
        <option value={value} key={value}>
          {text}
        </option>
      ))}
    </select>
  );
};

2.使用intersection(&)來讓target.value的型別不再只限於string

interface GeniricSelectProps<T> {
  options: { text: string; value: T }[];
  onSelect: (
    e: ChangeEvent<HTMLSelectElement> & { target: { value: T } }
  ) => void;
}

const GeniricSelect = <P extends string | number>({
  options,
  onSelect
}: GeniricSelectProps<P>) => {
  return (
    <select name="" id="" style={{ padding: 8 }} onChange={onSelect}>
      {options?.map(({ text, value }) => (
        <option value={value} key={value}>
          {text}
        </option>
      ))}
    </select>
  );
};


參考資料

Create a React / TypeScript Generic Component
How to set type for event.target.value?


#React #TypeScript #generic #Function component







Related Posts

React(9) - life cycle of class component

React(9) - life cycle of class component

在 Raspberry Pi 4 使用 TOTOLINK-N150UA-B

在 Raspberry Pi 4 使用 TOTOLINK-N150UA-B

Vue.js 學習旅程Mile 14 – Form Input Bindings 表單綁定篇:v-model

Vue.js 學習旅程Mile 14 – Form Input Bindings 表單綁定篇:v-model


Comments