[typescript] React Batter Wrapper Component (ComponentProps,ComponentPropsWithoutRef)
介紹
持續記錄在 udemy 的 ts 課程學到的東西,這次要介紹的是怎麼使用 ComponentPropsWithoutRef 來建立彈性化的 Wrapper Component。
問題點
現在有一個 Input component,程式碼如下,可以看到我們設定只能接收 id 和 label props。
type InputProps = {
id: string;
label: string;
};
const Input = ({ id, label, ...props }: InputProps) => {
return (
<p>
<label htmlFor={id}>{label}</label>
<input id={id} {...props} />
</p>
);
};
export default Input;
所以在使用 Input 的時候,我們只能傳入這兩個 props,如果傳入 type 或其他原生 HTML input 能夠使用的 attribute ,就會出現錯誤,因為 type 和 placeholder 並沒有定義在我們的 InputProps 裡面,但我們又不可能針對每一個 HTML element 的 attribute 去定義屬於它的 type。
import Input from "./components/Input";
function App() {
return (
<main>
<Input
id="name"
label="Your name"
type="text"
placeholder="Your name ..."
/>
<Input
id="age"
label="Your age"
type="number"
placeholder="Your age ..."
/>
</main>
);
}
export default App;
解決方法 (ComponentPropsWithoutRef)
還好 React 有提供 ComponentPropsWithoutRef 來解決這個問題,ComponentPropsWithoutRef 是一個泛型,使用的方法很簡單,只要傳入 HTML element 的 tag 名稱即可。
import { ComponentPropsWithoutRef } from "react";
type InputProps = {
id: string;
label: string;
} & ComponentPropsWithoutRef<"input">;
const Input = ({ id, label, ...props }: InputProps) => {
return (
<p>
<label htmlFor={id}>{label}</label>
<input id={id} {...props} />
</p>
);
};
export default Input;
現在回到 App,在 Input component 上按下 ctrl+i,就會看到原生 HTML input 能夠使用的 attribute 了。

補充
其實不只有 ComponentPropsWithoutRef 可以使用,還有 ComponentProps 和 ComponentPropsWithRef,用法也和字面上的意思一樣,如果 component 有傳入 ref 的話,就使用 ComponentPropsWithRef,差別就是讓維護的人看到就知道該 component 是需要接收 ref 的,而如果單純使用 ComponentProps 的話也可以,只是就沒辦法明確的知道 component 需不需要傳入 ref。
ComponentPropsWithoutRef 和 ComponentPropsWithRef 的缺點就是命名過長。
參考資料
ComponentProps: React's Most Useful Type Helper
udemy 課程 React & TypeScript - The Practical Guide Academind by Maximilian Schwarzmüller, Maximilian Schwarzmüller