8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React】memo()やstyled()を使うとジェネリクスが効かなくなる

Posted at

解決方法

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 Componentmemo()の戻り値の型を上書きします。

/** 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で型を上書きする方法にリアクションがたくさんついているので、現状はこの方法で凌ぐのが簡単そうです。

他に良さげな方法をご存じでしたらぜひ教えてください!

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?