JSX.ElementとReactNodeとReactElementをいつ使用するのですか?


166

現在、ReactアプリケーションをTypeScriptに移行しています。これまでのところ、これはかなりうまくrender機能しますが、関数の戻り値の型と関数コンポーネントに問題があります。

これまで、私は常にJSX.Element戻り値の型として使用していましたが、コンポーネントが何もレンダリングしないことを決定した場合、つまりreturnが有効な値ではないnullため、これnullは機能しなくなりましたJSX.Element。これが私の旅の始まりでした。ウェブを検索したところ、ReactNode代わりに使用する必要があることがわかりました。これにはnull、発生する可能性のあるその他のいくつかの事項も含まれています。これはより良い賭けのようでした。

ただし、関数コンポーネントを作成するときに、TypeScriptはReactNode型について文句を言います。繰り返しますが、私が見つけたいくつかの検索の後、関数コンポーネントにはReactElement代わりに使用する必要があります。しかし、そうすると、互換性の問題はなくなりますが、TypeScriptは再びnull有効な値ではないと文句を言います。

したがって、長い話を短くするために、3つの質問があります。

  1. JSX.ElementReactNodeとの違いは何ReactElementですか?
  2. renderクラスコンポーネントのメソッドがを返すReactNodeのに、関数コンポーネントが返すのはReactElementなぜですか?
  3. どうすればこれを解決できnullますか?

1
通常、コンポーネントで戻り値の型を指定する必要がないため、これが発生することに驚いています。コンポーネントに対してどのタイプの署名を行っていますか?class Example extends Component<ExampleProps> {クラスやconst Example: FunctionComponent<ExampleProps> = (props) => {関数コンポーネント(ExampleProps予想される小道具のインターフェイスはどこですか)のように見えるはずです。そして、これらの型には、戻り値の型を推測できる十分な情報があります。
ニコラスタワー

タイプはここで
JonasWilms19年

1
@NicholasTowerリンティングルールは、戻り値の型を明示的に提供することを強制します。そのため、これが発生します(コンパイラにすべてを推測させるよりも、実行することについてより多くのことを考え、理解に役立つため、これは良いことです)。 。
ゴロローデン

十分に公平なことですが、リンティングルールについては考えていませんでした。
ニコラスタワー

@JonasWilmsリンクをありがとう、しかし私はこれが私の質問に答えるとは思わない。
ゴロローデン

回答:


176

JSX.Element、ReactNode、ReactElementの違いは何ですか?

ReactElementは、タイプと小道具を持つオブジェクトです。

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

ReactNodeは、ReactElement、ReactFragment、文字列、ReactNodeの数値または配列、またはnull、未定義、またはブール値です。

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

JSX.ElementはReactElementであり、小道具の汎用型と型は任意です。さまざまなライブラリが独自の方法でJSXを実装できるため、JSXはグローバル名前空間であり、ライブラリによって設定されます。Reactは次のように設定します。

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}

例:

 <p> // <- ReactElement = JSX.Element
   <Custom> // <- ReactElement = JSX.Element
     {true && "test"} // <- ReactNode
  </Custom>
 </p>

クラスコンポーネントのレンダリングメソッドがReactNodeを返すのに、関数コンポーネントがReactElementを返すのはなぜですか?

確かに、彼らは異なるものを返します。Componentsリターン:

 render(): ReactNode;

そして、関数は「ステートレスコンポーネント」です。

 interface StatelessComponent<P = {}> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement | null;
    // ... doesn't matter
}

これは実際には歴史的な理由によるものです。

nullに関してこれを解決するにはどうすればよいですか?

ReactElement | nullreactと同じように入力します。または、Typescriptにタイプを推測させます。

タイプのソース


詳細な回答をありがとう!これは質問1に完全に答えますが、それでも質問2と3は未回答のままです。それらについてもご指導いただけますか?
ゴロローデン

@goloRoden確かに、私は今少し疲れていて、モバイルでタイプをスクロールするのに少し時間がかかります...;)
JonasWilms19年

こんにちは@JonasWilms。「そうではありません。ReactComponentは次のように定義されています:render():JSX.Element | null | false;」、どこに表示されますか?ReactNodeを私に返すようです(github.com/DefinitelyTyped/DefinitelyTyped/blob/…)また、マイナーなタイプミス、またはであるReactComponent必要があると思います。React.ComponentComponent
MarkDoliner19年

@MarkDoliner奇妙な、私はファイルからそのタイプをコピーしたことを誓うことができました...何であれ、あなたは完全に正しいです、私は編集します
JonasWilms19年

みんな、ウェブのどこかに、react && typescriptの公式ドキュメントはありますか?
Goran_Ilic_Ilke

30

1.)JSX.Element、ReactNode、ReactElementの違いは何ですか?

ReactElementとJSX.Element は、React.createElement直接またはJSXトランスパイルを介して呼び出した結果です。それは持つオブジェクトであるtypepropskeyJSX.ElementReactElement、そのpropstypeはタイプを持っているanyので、それらは多かれ少なかれ同じです。

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");

ReactNoderender()、クラスコンポーネントの戻り値の型として使用されます。これは、children属性のデフォルトタイプでもありますPropsWithChildren

const Comp: FunctionComponent = props => <div>{props.children}</div> 
// children?: React.ReactNode

React型宣言ではより複雑に見えますが、次と同等です。

type ReactNode = {} | null | undefined;
// super type `{}` has absorbed *all* other types, which are sub types of `{}`
// so it is a very "broad" type (I don't want to say useless...)

ほとんどすべてをに割り当てることができますReactNode。私は通常、より強力なタイプを好みますが、それを使用するためのいくつかの有効なケースがあるかもしれません。


2.)クラスコンポーネントのrenderメソッドがReactNodeを返すのに、関数コンポーネントがReactElementを返すのはなぜですか?

tl; dr:コアReactとは関係のない現在のTSタイプの非互換性です。

  • TSクラスコンポーネント:リターンReactNoderender()、リアクト/ JSよりもより寛大

  • TS関数コンポーネント:JSX.Element | nullReact / JSよりも制限の厳しい戻り値

原則としてrender()、React / JSクラスのコンポーネントは、関数コンポーネントと同じ戻り値の型サポートします。TSに関しては、さまざまなタイプが、歴史的な理由と下位互換性の必要性のために依然として保持されているタイプの不整合です。

理想的には、有効な戻り値の型はおそらく次のようになります。

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number 
  | boolean | null // Note: undefined is invalid

3.)nullに関してこれを解決するにはどうすればよいですか?

いくつかのオプション:
// Use type inference; inferred return type is `JSX.Element | null`
const MyComp1 = ({ condition }: { condition: boolean }) =>
    condition ? <div>Hello</div> : null

// Use explicit function return types; Add `null`, if needed
const MyComp2 = (): JSX.Element => <div>Hello</div>; 
const MyComp3 = (): React.ReactElement => <div>Hello</div>;  
// Option 3 is equivalent to 2 + we don't need to use a global (JSX namespace)

// Use built-in `FunctionComponent` or `FC` type
const MyComp4: React.FC<MyProps> = () => <div>Hello</div>;

注:回避しても、戻り値の型の制限から解放さReact.FC れるわけではありませんJSX.Element | null

Create React Appは、暗黙的な型定義のようないくつかの癖があるため、最近テンプレートから削除さReact.FCれました{children?: ReactNode}。したがって、React.FC控えめに使用することをお勧めします。

エッジケースでは、回避策としてタイプアサーションまたはフラグメントを追加できます。
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.