こんな感じのエラーを "@types/react": "^17.0"
を指定せずに倒せるようになった!!!
Type error: 'SomeComponent' cannot be used as a JSX component. Its return type 'ReactNode' is not a valid JSX element. Type 'undefined' is not assignable to type 'Element | null'.
まとめ
一番最初に対応方法のまとめを書いておく
jsxImportSource
を弄っていない場合typesciprt
を 5.1、@types/react
を 18.2.8 にアップデートする
jsxImportSource
を弄っている場合typesciprt
を 5.1、@types/react
を 18.2.8 にアップデートするJSX.ElementType
を宣言する
例えば2023年6月4日時点で @emotion/react
を使っている場合、アップデートを行った後に以下のように宣言する。
// src/types/emotion-react-jsx-runtime.d.ts import { JSX } from '@emotion/react/jsx-runtime'; declare module '@emotion/react/jsx-runtime' { namespace JSX { type ElementType = React.JSX.ElementType; } }
TypeScript 5.1 JSX.ElementType
TypeScript 5.1がリリースされた。この新機能の一つとして、JSXのElementとして妥当かどうか確かめるための新しい型 JSX.ElementType
の存在が導入された。
これまで、関数コンポーネントは null | JSX.Element
を返すことが強制されていた。例えば、const Component = () => 42; <Component />
という例での Component
は42を返し、これは null | JSX.Element
には該当しないため、型チェックの段階で弾かれる。
ただ、Reactで作成する関数コンポーネントは ReactNode
を返す。この型はおおよそ Element | number | string | Iterable<ReactNode> | undefined
(将来的には Promise<ReactNode>
) のようになっており、上記で記した型と一致しない。ここで、ReactNode
も問題なくコンポーネントとして利用できるようにしたいモチベーションが発生する。
この解決策として、TypeScript 5.1以降より、TypeScriptのjsx周辺で型 JSX.ElementType
を使うようになった。各種 jsx-runtime
を提供するライブラリは JSX.ElementType
を埋めるように宣言をする必要がある。JSX.ElementType
を使ってコンポーネントを検査した結果、パスしたのであれば、そのコンポーネントはそのランタイムで使用可能だということが分かる、という流れ。例えばReactでこれを用意するなら以下のような感じになる。
// inlined `React.JSXElementConstructor` type ReactJSXElementConstructor<Props> = | ((props: Props) => React.ReactNode) | (new (props: Props) => React.Component<Props, any>); declare global { namespace JSX { type ElementType = string | ReactJSXElementConstructor<any>; } }
stringや、ReactNode
を返す関数コンポーネントや、クラスコンポーネントに対応している型を用意し、JSX.ElementType
として宣言する。これで、ReactのJSXランタイムは、Reactで作成するような関数コンポーネントをそのまま扱えるようになった。
@types/react
18.2.8 JSX.ElementType
実際に、@types/react
18.2.8 のリリースでは、コンポーネントの返り値として ReactNode
を直接利用できるような ElementType
を宣言するようになった。抜粋するとこんなかんじ。
type JSXElementConstructor<P> = | ((props: P, deprecatedLegacyContext?: any) => ReactNode) | (new (props: P) => Component<any, any>); namespace JSX { type ElementType = string | React.JSXElementConstructor<any>; }
Emotionで JSX.ElementType
に対応してみる
追記: 2023年6月7日に下部のセクションで出したPRがマージされたので、@emotion/react
がv11.11.1より大きければ既に対応されているはず!!
これで万事解決かと思い、自分のNext.jsのプロジェクト内の typescript
を 5.1.3
に、@types/react
を 18.2.8
にアップグレードしたが、変わらず同じ問題が発生した。端的に言うと、Emotionが関連した問題だった。
今回のプロジェクトではEmotionを使用している。Emotionは tsconfig.json
の jsxImportSource
に対して @emotion/react
などと指定している。
@emotion/react
では、コンポーネントのpropとして新規に css
が指定できるようになっている。この解決のため、任意の方法でjsxのランタイムを差し替え、新規に css
propを指定できるように変更が入っている。差し替え方法の一例としては上記のように、jsxImportSource
に @emotion/react
を指定する。これによってjsxのランタイムとして @emotion/react/jsx-runtime
が利用されるようになる。
ランタイム内では、基本的にはReactのランタイムのJSXをそのまま利用しているが、IntrinsicElements
に対しては追加で手を入れており、これによって css
prop の利用を実現している。
// 事前に `type ReactJSXElement = JSX.Element` みたいにReactが宣言した型を `ReactXXXX` の型に再代入している // ↓後に JSX という名前でexportされる export namespace EmotionJSX { interface Element extends ReactJSXElement {} interface ElementClass extends ReactJSXElementClass {} interface ElementAttributesProperty extends ReactJSXElementAttributesProperty {} interface ElementChildrenAttribute extends ReactJSXElementChildrenAttribute {} type LibraryManagedAttributes<C, P> = WithConditionalCSSProp<P> & ReactJSXLibraryManagedAttributes<C, P> interface IntrinsicAttributes extends ReactJSXIntrinsicAttributes {} interface IntrinsicClassAttributes<T> extends ReactJSXIntrinsicClassAttributes<T> {} // ここで `css` prop を使えるようにしている! type IntrinsicElements = { [K in keyof ReactJSXIntrinsicElements]: ReactJSXIntrinsicElements[K] & { css?: Interpolation<Theme> } } }
ここで問題なのは、@emotion/react/jsx-runtime
が提供するJSXには、2023年6月4日時点では ElementType
が宣言されていないということだった。ただ、先程提示した通り、基本的にはReactが宣言した型をそのまま使いまわしているだけなので、追加で ElementType
を宣言してやれば良い。もちろん、事前に typescript
を 5.1 に、 @types/react
を 18.2.8 にアップデートしておく必要がある。
// Emotionが定義したJSXをそのまま持ってくる import { JSX } from '@emotion/react/jsx-runtime'; declare module '@emotion/react/jsx-runtime' { namespace JSX { // 追加で `ElementType` を宣言する type ElementType = React.JSX.ElementType; } }
Emotion側に対応してもらいたい
追記: マージされた!!!!!!v11.11.1からは素の状態で対応されているはず。
せっかくならEmotion側で対応してほしい。
2023年6月4日時点の @emotion/react
からの @types/react
への依存状況はオプショナルなものになっており、しかもバージョンの指定も存在しない。なので、18.2.8以上に依存することがないように ElementType
をライブラリ内で宣言するような形で対応するPRを出した。
正直、JS関連のライブラリに対する文化などを知らないので、これが歓迎されるものなのかはわからないが、マージされればいいな~くらいの気持ち。マージされたら最初に書いた対応も必要なくなりそう。