Open4

多相的forwardRefの型付けメモ

たふみたふみ

何年もforwardRefとジェネリクスの相性問題は語られてきたが、最近自分の中でほぼ解決している書き方があるので書き残しておく

なおあちこちのものを参照していて1つ1つどれも私が思いついたものではないけど、もうどこを参照したか思い出せないのでScrapで手軽に書いてます。許して…

たふみたふみ

as propsを使わない場合

import { FC } from 'react'

type Ref<As extends ElementType> = ComponentPropsWithRef<As>['ref'];

type XProps<T,> = {
  ...
}

type XComponent = <T,>(props: XProps<T>) => ReturnType<FC>

const X: XComponent = forwardRef(<T,>({...props}: XProps<T>, ref: Ref<...>) => (...)
たふみたふみ

as propsを使う場合

import { ComponentPropsWithoutRef, ComponentPropsWithRef, ElementType, FC } from 'react';

type PreservedOmit<T, K extends keyof T> = {
  [Property in keyof T as Exclude<Property, K>]: T[Property];
};

type Ref<As extends ElementType> = ComponentPropsWithRef<As>['ref'];

type RefProp<As extends ElementType> = { ref?: Ref<As> };

type AsProp<As extends ElementType> = {
  /**
   * レンダリングするコンポーネントを指定 (例: button, a, input)
   */
  as?: As;
};

type PolyComponentProps<Props extends object, As extends ElementType> = AsProp<As> &
  Props &
  RefProp<As> &
  PreservedOmit<ComponentPropsWithoutRef<As>, keyof (Props & AsProp<As> & RefProp<As>)>;

type XProps<T, As extends ElementType = 'button'> = PolyComponentProps<{...}, As>

type XComponent = <T, As extends ElementType = 'button'>(props: XProps<T, As>) => ReturnType<FC>

const X: XComponent = forwardRef(<T, As extends ElementType = 'button'>({ ...props }: XProps<T, As>, ref: Ref<As>) => {...}
たふみたふみ

ReturnType<FC> してるのは、前は JSX.Element | null とかだったけどTypeScript 5.3?あたりからReactNodeを返すようになってちょっと事情が変わり、JSX.ElementType でいいみたいな話もあったけどなんかよう動かんときもあるので、ReturnType<FC> にするのが1番楽で筋が良く問題が起きにくい解決法かなと思ったからです