何时使用 JSX.Element vs ReactNode vs ReactElement?

2022-08-29 23:02:55

我目前正在将 React 应用程序迁移到 TypeScript。到目前为止,这工作得很好,但我对函数的返回类型有问题,特别是在我的函数组件中。render

我一直用作返回类型,现在如果组件决定呈现任何内容,即返回,则不再起作用,因为 不是 的有效值。这是我旅程的开始。我搜索了网络,发现你应该改用,其中包括一些可能发生的其他事情。JSX.ElementnullnullJSX.ElementReactNodenull

但是,在创建功能组件时,TypeScript 会抱怨该类型。同样,经过一些搜索,我发现对于功能组件,您应该使用。但是,如果我这样做,兼容性问题就消失了,但现在TypeScript再次抱怨不是一个有效的值。ReactNodeReactElementnull

长话短说,我有三个问题:

  1. 和 之间有什么区别?JSX.ElementReactNodeReactElement
  2. 为什么类组件的方法返回,而函数组件返回?renderReactNodeReactElement
  3. 我如何解决这个问题?null

答案 1

JSX有什么区别。Element, ReactNode and ReactElement?

ReactElement是具有类型和道具的对象。

 type Key = string | number

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

ReactNode 是 ReactElement、ReactFragment、字符串、ReactNodes 的数字或数组,或者 null,或者 unfined,或者一个布尔值:

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

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

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

新浪网.Element 是一个 ReactElement,props 的泛型类型和类型是 any。它存在,因为各种库都可以以自己的方式实现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?

事实上,它们确实返回了不同的东西。s 返回:Component

 render(): ReactNode;

函数是“无状态组件”:

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

这实际上是由于历史原因

如何解决空值问题?

像 react 一样键入它。或者让Typescript推断类型。ReactElement | null

类型的源


答案 2

1.) JSX 有什么区别。Element, ReactNode and ReactElement?

ReactElement 和 JSX.Element是直接或通过JSX转译调用React.createElement的结果。它是一个带有 、 和 的对象。新浪网.元素是 ,其 和 具有类型,因此它们或多或少是相同的。typepropskeyReactElementpropstypeany

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

ReactNode 被用作类组件的返回类型。它也是 PropsWithChildren 属性的默认类型。render()children

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.) 为什么类组件的渲染方法返回 ReactNode,而函数组件返回 ReactElement?

tl;dr:它是当前 TS 类型的不兼容性,与核心 React 无关

  • TS 类组件:返回 ,比 React/JS 更宽松ReactNoderender()

  • TS 函数组件:返回,比 React/JS 更严格JSX.Element | null

原则上,在 React/JS 类中,组件支持与函数组件相同的返回类型。关于 TS,由于历史原因和对向后兼容性的需求,不同的类型仍然保持不一致。render()

理想情况下,有效的返回类型可能看起来更像这样:

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

3.) 我如何解决与空值相关的问题?

一些选项:
// 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.FCJSX.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`