目錄
Generic component是什麼
純Typescript有泛型的功能,用Typescript
寫的Reactcomponent
也可以和泛型結合
這個技巧相當地實用,在一些UI library內也會看到
泛型的React component幾個優點
- 利於
複用
- 維持
型別彈性
,同時
也避免過於彈性,故能避開
潛在的型別錯誤
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?