今回は ReactNodeによる型定義
について調べました。
何があったの?
子
で受け取る型をどうすればよいのか悩んでいました。
調べていく中で、どうやら React.ReactNode
を使うのがよいとわかりました。
type Props = {
children: React.ReactNode // ここで使う
}
const Kodomo: VFC<Props> = ({ children }) => { ~~
この指定にて、
バインド要素 'children' には暗黙的に 'any' 型が含まれます。
といったようなエラーが消えて嬉しかったのを覚えています。
そこで、 ReactNode
とは何なのかを少し掘り下げてみることにしました。
調べる
React.ReactNodeの中身
// /node_modules/@types/react/index.d.ts 241行目
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
React.ReactNodeの中身を見てみると、様々な型がユニオンで指定されていました。
そこで一つ疑問が生まれました。
なるほど、子の型にReactNode
って付けておけば、一旦大丈夫そうだな。
でも、子供に渡すモノよってはReactNode
を使うのは大袈裟すぎない?
もっと型を絞ったほうが記述者の意図が伝わりやすく、後からメンテしやすいはずでは。
React.ReactChildを使ってみる
// /node_modules/@types/react/index.d.ts 234行目辺り
type ReactChild = ReactElement | ReactText; // ここをみてみる
type ReactFragment = {} | Iterable<ReactNode>;
interface ReactPortal extends ReactElement {
key: Key | null;
children: ReactNode;
}
ReactChild
も ReactElement | ReactText
がユニオンで指定されていました。
ということで早速、「string
しか受け取らない子」を作って、試してみます!
(一番下にSampleコード一式置いておきます)
React.ReactNode
でも React.ReactChild
どちらでもエラーが無いはずです。
// 親
const Oya: NextPage = () => {
return (
<div>
<Kodomo>
子供で表示するテキスト
</Kodomo>
</div>
)
}
// 子供 (sampleのためinport無しの直書き)
type Props = {
// children: React.ReactNode // OK
children: React.ReactChild // OK
}
const Kodomo = ({ children }: Props) => {
return <span>{children}</span>
}
エラー無く通りました。これで、1つ制作者の意図に近づきました!!
が、、、ReactElementとReactText
の両方使う想定なのだろうか?と疑問を残しそうです。
後からこのコードをお世話するどなたか(自分含む)ためにも、わかりやすくしておきたい。
React.ReactTextを使ってみる
ReactText
はシンプルです。 string | number
がまとまったものでした。
// /node_modules/@types/react/index.d.ts 233行目
type ReactText = string | number;
// 子供
type Props = {
children: React.ReactText // OK
}
エラーなし!
文字か数字を出すためだけに作ったシンプルなコンポーネント
という制作者の意図が伝わってきました!
、、、で、本当は文字か数字
どっちを出したかったんだい?
type Props = {
children: string // OK
children: number // NG
// 'Kodomo' コンポーネントには、テキストを子要素として指定できません。JSX のテキストには 'string' 型が含まれていますが、'children' の予期された型は 'number' です。
}
結局こうなりました。
string
しか受け取らない子 の時は string
だけを渡せばOKです。
制作者の意図が完全に伝わってきました。
まとめ
汎用的に使う子コンポーネント、型が決まっている子コンポーネント色々ありますが、
意図を伝え残すためにも型定義をReact.ReactNode
だけに頼らないほうがよいという考えです。
ここは改善した方がいいよ!こういった考え方もあるよ!というご意見はあるかと思います!
ぜひコメントください!今後の参考にいたします!
参考
React の props.children の型定義には ReactNode を使う
雰囲気で使わない @types/react
今回のsampleコード
// 環境
"next": "12.1.1",
"react": "17.0.2",
"@emotion/react": "^11.8.2",
"typescript": "4.6.3"
// Next.js
import type { NextPage } from 'next'
import { VFC } from 'react'
import { css } from "@emotion/react"
const styles = {
padding: (size: number) => css`
padding: ${size / 10}rem;
`,
baseInner: css`
border: 1px solid #000;
`,
}
// 親
const Oya: NextPage = () => {
return (
<div css={[styles.baseInner, styles.padding(6)]}>
<Kodomo>
ReactElement
</Kodomo>
</div>
)
}
// 子供
type Props = {
children: React.ReactNode // or ReactChild or ReactText or string
}
const Kodomo: VFC<Props> = ({ children }) => {
return <span>{children}</span>
}
export default Oya