解決方法
memo()
の戻り値の型をas typeof Component
で上書きします。
/** React `memo()` */
const MemoButton = memo(Button) as typeof Button;
/** MUI(Emotion) `styled()` */
const StyledButton = styled(Button)({ ... }) as typeof Button;
/** MobX React `observer()` */
const ObserverButton = observer(Button) as typeof Button;
初めに
内部のコンポーネントを上書きする仕組みを提供するためにTypeScriptのジェネリクスを利用してコンポーネントを作る事があると思いますが、後に最適化のためmemoを使用したらジェネリクスが機能しなくなりました。
ジェネリックなコンポーネントとは
例えば、React.ElementType
を継承するT
のジェネリックなHoge
を定義して、そのプロパティにReact.ComponentPropsWithRef<T>
を指定しておいて、
function Hoge<T extends React.ElementType = 'div'>({ component, ...props }: { component: T } & React.ComponentPropsWithRef<T>) {
const Component = component ?? 'div';
return <Component {...props} />
}
component
に特定のコンポーネントを指定したら、そのコンポーネントのプロパティがHoge
のプロパティとして補完に表示されるようになります。
function Fuga({ property1 }: { property1: string }) { ... }
// `property1`(`Fuga`のプロパティ)が`Hoge`のプロパティに追加され、補完に表示される。
<Hoge component={Fuga} property1="1234">...</Hoge>
私はMUIをよく利用するのですが、MUIとReact Routerでよく以下のようにしてボタンリンクを簡単に作成しています。
// `to`(`Link`のプロパティ)が`Button`のプロパティに追加され、補完に表示される。
// 必須なので指定していないと怒られる。
<Button component={Link} to="/profile">プロフィール</Button>
memo()
やstyled()
と併用できない
ジェネリックなコンポーネントは簡単に動作を上書き出来て便利ですが、memo()
やstyled()
を使うとジェネリクスが機能しなくなります。
/** React `memo()` */
const MemoButton = memo(Button);
/** MUI(Emotion) `styled()` */
const StyledButton = styled(Button)({});
/** MobX `observer()` */
const ObserverButton = observer(Button);
// OK
<Button component={Link} to="/profile">プロフィール</Button>
// NG
// `to`が存在しないと言われる。
<MemoButton component={Link} to="/profile">プロフィール</Button>
// NG
<StyledButton component={Link} to="/profile">プロフィール</Button>
// NG
<ObserverButton component={Link} to="/profile">プロフィール</Button>
as typeof Component
で型を上書きする
memo()
を使わないわけにはいかないので、少々強引ですがas typeof Component
でmemo()
の戻り値の型を上書きします。
/** React `memo()` */
const MemoButton = memo(Button) as typeof Button;
/** MUI(Emotion) `styled()` */
const StyledButton = styled(Button)({}) as typeof Button;
/** MobX `observer()` */
const ObserverButton = observer(Button) as typeof Button;
終わりに
この問題について#37087でも議論されていますが、as typeof
で型を上書きする方法にリアクションがたくさんついているので、現状はこの方法で凌ぐのが簡単そうです。
他に良さげな方法をご存じでしたらぜひ教えてください!