たとえば、以下のようなジェネリクスを使ったコンポーネントを考えます。
<Component<string> prop1="string" prop2="string" ref={ref} />
Component の prop1 と prop2 は同じ T 型とします(上の例では T は string)。
ref がなければカンタンに実装できる
ref さえなければ簡単に実装できます。
type Props<T> = { prop1: T; prop2: T; }; const Component = <T,>({ prop1, prop2 }: Props<T>) => ( <ul> <li>prop1: {JSON.stringify(prop1)}</li> <li>prop2: {JSON.stringify(prop2)}</li> </ul> ); <Component prop1="foo" prop2="bar" /> // 通る <Component prop1="foo" prop2={42} /> // ちゃんと型エラーになる
シンプルですね。
forwardRef が挟まるとジェネリクスが消える
forwardRef でラップするとジェネリクスが効かなくなります。
const ComponentWithRef = React.forwardRef(Component); <ComponentWithRef prop1="foo" prop2={42} ref={ref}/>; // 型エラーになってくれない…
forwardRef が返すコンポーネントの型を指定する
これを回避するために、自分で forwardRef が返すコンポーネントの型を指定します。
type Props<T> = { prop1: T; prop2: T; }; const Component = <T,>({ prop1, prop2 }: Props<T>) => ( <ul> <li>prop1: {JSON.stringify(prop1)}</li> <li>prop2: {JSON.stringify(prop2)}</li> </ul> ); const ComponentWithRef: <T,>( props: Props<T> & { ref: Ref<unknown> } ) => JSX.Element | null = forwardRef(Component);
おまけ: React.memo の場合
上の手法は React.memo でも使えますが、React.memo の場合は単に typeof を使う方がカンタンです。
const MemoizedComponent: typeof ComponentWithRef = React.memo(ComponentWithRef); <MemoizedComponent prop1="foo" prop2={42} ref={ref} />; // きちんと型エラーになる
forwardRef と異なり React.memo でラップする前後で型は変わらないためうまく動きます。