Mr. Panda
Tech For Fun

[React]JSX.Element vs ReactElement vs ReactNode

JSX.Element、ReactElement、ReactNode这三种类型通常会使新手 React 开发人员感到困惑。似乎它们是同一个东西,只是名称不同。但这并不完全正确。下面详细对比下这三种类型。

JSX.Element对比ReactElement

这两种类型都是React.createElement()/jsx()函数调用的结果。

它们都是具有以下特征的对象:

  • type
  • props
  • key
  • 一些其他“隐藏”属性,如 ref、$$typeof 等

ReactElement

ReactElement类型是最基本的。它在 React 源代码中使用 flow 定义。

// ./packages/shared/ReactElementType.js

export type ReactElement = {|
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
  // ReactFiber
  _owner: any,

  // __DEV__
  _store: {validated: boolean, ...},
  _self: React$Element<any>,
  _shadowChildren: any,
  _source: Source,
|};

这种类型也在DefiniteTyped 包中定义。

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

JSX.Element

它是更通用的类型。主要区别在于propstype的类型为any

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

这为不同库如何实现 JSX 提供了灵活性。例如,Preact 有自己的实现,具有不同的 API

ReactNode

ReactNode类型是另一回事。它不是React.createElement()/jsx()函数调用的返回值。

const Component = () => {
  // ReactElement
  return <div>Hello world!</div>
}

// ReactNode
const Example = Component();

React 节点本身就是虚拟 DOM 的一种表示。ReactNode组件的所有可能返回值的集合也是如此。

type ReactChild = ReactElement | ReactText;

type ReactFragment = {} | Iterable<ReactNode>;

interface ReactPortal extends ReactElement {
  key: Key | null;
  children: ReactNode;
}

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

children用什么类型?

一般来说,ReactNode是正确的children的类型。它提供了最大的灵活性,同时保持正确的类型检查。但它有一个警告,因为ReactFragment允许一个{}类型。

const Item = ({ children }: { children: ReactNode }) => {
  return <li>{children}</li>;
}

const App = () => {
  return (
    <ul>
      // Run-time error here, objects are not valid children!
      <Item>{{}}</Item>
    </ul>
  );
}

在使用 TypeScript 和 React 时,React 已经为带有子组件的组件提供了类型,因此也可以不必自己传入类型。所以也可以:

import type { FC } from "react";

const Item: FC = ({ children }) => <li>{children}</li>;

如果你想变得更灵活,你可以使用JSX.IntrinsicElements “扩展”类型:

import type { FC } from "react";

const Item: FC<JSX.IntrinsicElements["li"]> = props => <li {...props} />;

如果使用没有子元素的元素(如input),则可以使用VFC而不是FC,如下所示:

import type { VFC } from "react";

const Input: VFC<JSX.IntrinsicElements["input"]> = props => (
    <input {...props} />
);

Jonsam

一个理科IT宅男,喜欢旅游、分享和美食,做点想做的事情,遇见想见的人。

🍒 美食 | 🌐 FE | 🕌 旅行 | 💻 加班 | ♍ 处女座

jonsam ng

jonsam ng

文章作者

海阔凭鱼跃,天高任鸟飞。

[React]JSX.Element vs ReactElement vs ReactNode
JSX.Element、ReactElement、ReactNode这三种类型通常会使新手 React 开发人员感到困惑。似乎它们是同一个东西,只是名称不同。但这并不完全正确。下面详细对比下这三种类型。 JSX.Ele…
扫描二维码继续阅读
2022-02-26