React 组件类型定义的区别:JSX.Element vs ReactNode vs ReactElement

3,585 阅读2分钟

本文翻译整理自 StackOverflow 问答 When to use JSX.Element vs ReactNode vs ReactElement?

组件类型定义

很多人可能在使用 TypeScript 编写 React 应用的时候会对三种不同的函数返回值类型产生困惑,不明白它们之间的区别以及应该什么时候使用哪一种类型才比较严谨。

ReactElement 是含有 props 和 type 属性的对象:

type Key = string | number

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

ReactNode 则是多种类型的集合:

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

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

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

类组件的 render 成员函数会返回 ReactNode 类型的值,而且 PropsWithChildren 类型中指定的 children 类型也是 ReactNode。

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

type PropsWithChildren<P> = P & {
  children?: ReactNode;
}

虽然 React 的类型定义看起来写得很复杂,但它实际上等价于:

type ReactNode = {} | null | undefined;

由于 {} 是所有对象的原型,你可以把几乎任何类型赋值给 ReactNode,但绝大多数情况下应该对它进行更详细的类型声明。

JSX.Element 通过执行 React.createElement 或是转译 JSX 获得。

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");
<p> // <- ReactElement = JSX.Element
  <Custom> // <- ReactElement = JSX.Element
    {true && "test"} // <- ReactNode
  </Custom>
</p>

JSX 是一个全局的命名空间,不同的库对 JSX 都可以有自己不同的实现,而 React 的实现方式就是让 JSX.Element 等价于 ReactElement,同时将它的泛型 props 和 type 都设为 any:

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

返回类型的不同

有的同学可能会注意到:类组件渲染方法的返回值类型和函数组件的是不一样的,这是因为目前版本的 TypeScript 类型定义并不能准确地限定 React 实际值的范围:

  • 类组件类型定义:通过 render() 返回 ReactNode,比 React 的实际值范围更宽松
  • 函数组件类型定义:返回 JSX.Element,也比 React 的实际值范围更宽松

实际上 React 类组件中的 render() 和函数组件的返回类型是一样的,而 TypeScript 只是出于历史原因和向后兼容需要,为不同种类的组件声明了不同的返回值类型。

根据 文档的规定 我们可以为组件返回值给出准确的类型定义:

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number | boolean | null
// 注意: 不能传入 undefined