react-hook-form というライブラリを使ってフォームを作っていたのですが、input や textarea が Atom レベルのコンポーネントになっており、ライブラリの都合で ref を渡す必要があったので forwardRef の型を頑張ったメモ。
"react": "17.0.2" "typescript": "4.4.2"
forwardRef<refの対象, props> で書けば OK
type ButtonProps = JSX.IntrinsicElements['button']; const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>( (props, ref) => { return <button {...props} ref={ref} /> } );
ESLint で missing display name というエラーになる
ESLint の設定では次のようなエラーが表示されます。
Component definition is missing display name eslint(react/display-name)
DisplayName allows you to name your component. This name is used by React in debugging messages.
cf. eslint-plugin-react/display-name.md at master · yannickcr/eslint-plugin-react · GitHub
デバック時に関数名が無いと分かりづらいから関数名をつけることを強制するルールのようです。
つまり forwardRef で返す関数が無記名関数なアロー関数になっているので、ここに関数名を付けろということっぽい。
1. アロー関数をやめる
const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>( - (props, ref) => { + (function ButtonComponent(props, ref) { return <button {...props} ref={ref} /> } );
エラーにならないし、スコープが違うから問題ないのだけれど ButtonComponent
という変数と function ButtonComponent
があってちょっと気持ち悪いかも?
2. displayName プロパティを付けてしまう
const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
return <button {...props} ref={ref} />
}
);
+ ButtonComponent.displayName = 'ButtonComponent';
これはこれで無理矢理感ある気もするけれど…
ESLint のドキュメントには上記2つの書き方が載っていたので、どちらでも良さそうです。 (個人的にどちらもあまりしっくりこない)
おまけ forwardRef の型定義を見てみる
forwardRef
interface ForwardRefExoticComponent<P> extends NamedExoticComponent<P> { defaultProps?: Partial<P> | undefined; propTypes?: WeakValidationMap<P> | undefined; } function forwardRef<T, P = {}>( render: ForwardRefRenderFunction<T, P> ): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
T
と P
という型を受け取って ForwardRefRenderFunction<T, P>
という型の render を引数にとって、ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>
型を返す。
forwardRef()
内に渡すコンポーネントの部分は ForwardRefRenderFunction<T, P>
。
ForwardRefRenderFunction
type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null; interface ForwardRefRenderFunction<T, P = {}> { (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null; displayName?: string | undefined; // explicit rejected with `never` required due to // https://github.com/microsoft/TypeScript/issues/36826 /** * defaultProps are not supported on render functions */ defaultProps?: never | undefined; /** * propTypes are not supported on render functions */ propTypes?: never | undefined; }
forwardRef()
にわたすコンポーネントの部分は (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null => {...}
。displayName
のプロパティがあるから function Foo()
を渡しても OK ってことかな? (理解できているわけではない)
forwardRef<T, P>
なのにコンポーネント部分で (P, T) => {}
になってるの紛らわしくないですか???
所管
Chakra UI とか使ってたらよしなに定義されてるから気にしてなかったけど、汎用性の有るコンポーネント作るのめちゃめちゃ難しい…
本件と関係ないけど react-hook-form 色んなものが any 型に推論されるせいでめちゃめちゃ難しい。どうやって使うの??? TypeScript なんもわからんくなった…
TypeScript まだまだ出来ないので型問題でハマるとめちゃめちゃ疲れる。
[参考]
- React Top-Level API – React
- Home | React Hook Form - Simple React forms validation
- Reactでウェブフォームを作る2021
- Forwarding React Refs with TypeScript | Building SPAs
- javascript - How to use TypeScript and forwardRef in ReactJS? - Stack Overflow
- TypeScript で React.forwardRef を簡潔に書く方法
- eslint-plugin-react/display-name.md at master · yannickcr/eslint-plugin-react · GitHub
- reactjs - Component definition is missing display name for forwardRef - Stack Overflow
- 【React】自作コンポーネントに ref 属性を渡す方法 | White Space
- Stateless Functional Component には関数名に名前を付ける - Qiita