Next.jsでJSX.ElementとReactNodeの差異を考える

そもそもReactNodeとかReact Elementの話

ReactPortal云々でエラーが出るのはReactNode型がReactPortalを含むUnionだから

ReactElementについて
ReactElementはJSXの糖衣で表現されることが多いが、極論ただのJSオブジェクト
// これで返ってくる値はReactElement
const element = createReactElement()

@types/react@18.3.18
においては、JSX.ElementとReactElementはほぼ等価(あくまでもReact内部での話)
declare global {
/**
* @deprecated Use `React.JSX` instead of the global `JSX` namespace.
*/
namespace JSX {
// We don't just alias React.ElementType because React.ElementType
// historically does more than we need it to.
// E.g. it also contains .propTypes and so TS also verifies the declared
// props type does match the declared .propTypes.
// But if libraries declared their .propTypes but not props type,
// or they mismatch, you won't be able to use the class component
// as a JSX.ElementType.
// We could fix this everywhere but we're ultimately not interested in
// .propTypes assignability so we might as well drop it entirely here to
// reduce the work of the type-checker.
// TODO: Check impact of making React.ElementType<P = any> = React.JSXElementConstructor<P>
type ElementType = string | React.JSXElementConstructor<any>;
interface Element extends React.ReactElement<any, any> {}

React.JSX.Elementは異なる
namespace JSX {
type ElementType = GlobalJSXElementType;
interface Element extends GlobalJSXElement {}

このGlobalJSXElement
も結局はJSX.Element
// React.JSX needs to point to global.JSX to keep global module augmentations intact.
// But we can't access global.JSX so we need to create these aliases instead.
// Once the global JSX namespace will be removed we replace React.JSX with the contents of global.JSX
interface GlobalJSXElement extends JSX.Element {}

Why overriding
グローバルモジュールの拡張を維持するためにReact.JSX は global.JSX を指す必要があるが、global.JSX に直接アクセスできないため、代わりにエイリアスを作成し、将来的に global JSX の名前空間が削除された際には React.JSX を global.JSX の内容に置き換える予定である。
// React.JSX needs to point to global.JSX to keep global module augmentations intact.
// But we can't access global.JSX so we need to create these aliases instead.> // Once the global JSX namespace will be removed we replace React.JSX with the contents of global.JSX

多分だけど漏れなくこれが関係している

const comp: React.FC = () => {}
と const comp = () : JSX.Element => {}
の違い

type FC<P = {}> = FunctionComponent<P>;

FunctionComponentのdefinition
interface FunctionComponent<P = {}> {
(
props: P,
/**
* @deprecated
*
* @see {@link https://legacy.reactjs.org/docs/legacy-context.html#referencing-context-in-lifecycle-methods React Docs}
*/
deprecatedLegacyContext?: any,
): ReactNode;
}
要するに上記だけ

結論差異は、ReactNode
vs JSX.Element

ReactNode
の定義を振り返る

@types/react@18.3.18
の話
type ReactNode =
| ReactElement
| string
| number
| Iterable<ReactNode>
| ReactPortal
| boolean
| null
| undefined
| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES[
keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES
];

ReactNodeの方が受け口が広い

DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES
これはrelease channelの差異で起きるReactNodeのunionに含めておきたい値を逃しておく場所

React.FCは戻り値に広めの方を使える、大きくうけて小さく出したいならJSX.Elementだけど正直そんな旨味なさそうだからReact.FCが良さそう