When to use JSX.Element vs ReactNode vs ReactElement?

前端 未结 2 767
面向向阳花
面向向阳花 2020-12-04 05:44

I am currently migrating a React application to TypeScript. So far, this works pretty well, but I have a problem with the return types of my render functions re

2条回答
  •  南方客
    南方客 (楼主)
    2020-12-04 06:20

    1.) What is the difference between JSX.Element, ReactNode and ReactElement?

    ReactElement and JSX.Element are the result of invoking React.createElement directly or via JSX transpilation. It is an object with type, props and key. JSX.Element is ReactElement, whose props and type have type any, so they are more or less the same.

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

    ReactNode is used as return type for render() in class components. It also is the default type for children attribute with PropsWithChildren.

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

    It looks more complicated in the React type declarations, but is equivalent to:

    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...)
    

    You can assign almost everything to ReactNode. I usually would prefer stronger types, but there might be some valid cases to use it.


    2.) Why do the render methods of class components return ReactNode, but function components return ReactElement?

    tl;dr: It is a current TS type incompatibility not related to core React.

    • TS class component: returns ReactNode with render(), more permissive than React/JS

    • TS function component: returns JSX.Element | null, more restrictive than React/JS

    In principle, render() in React/JS class components supports the same return types as a function component. With regard to TS, the different types are a type inconsistency still kept due to historical reasons and the need for backwards-compatibility.

    Ideally a valid return type would probably look more like this:

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

    3.) How do I solve this with respect to null?

    Some options:
    // Use type inference; inferred return type is `JSX.Element | null`
    const MyComp1 = ({ condition }: { condition: boolean }) =>
        condition ? 
    Hello
    : null // Use explicit function return types; Add `null`, if needed const MyComp2 = (): JSX.Element =>
    Hello
    ; const MyComp3 = (): React.ReactElement =>
    Hello
    ; // 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 = () =>
    Hello
    ;

    Note: Avoiding React.FC won't save you from the JSX.Element | null return type restriction.

    Create React App recently dropped React.FC from its template, as it has some quirks like an implicit {children?: ReactNode} type definition. So using React.FC sparingly might be preferable.

    In edge cases, you can add a type assertion or Fragments as workaround:
    const MyCompFragment: FunctionComponent = () => <>"Hello"
    const MyCompCast: FunctionComponent = () => "Hello" as any 
    // alternative to `as any`: `as unknown as JSX.Element | null`
    

提交回复
热议问题