什么时候使用JSX.Element vs ReactNode vs ReactElement?

Gol*_*den 11 javascript typescript reactjs

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

到目前为止,我一直将其JSX.Element用作return类型,现在,如果组件决定呈现任何内容(即return)nullnull则此方法将不再起作用,因为没有有效的value JSX.Element。这是我旅程的开始,因为现在我在网上搜索并发现您应该使用ReactNode它,它还包括null可能发生的其他一些事情。这似乎是更好的选择。

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

因此,总而言之,我有三个问题:

  1. 之间有什么区别JSX.ElementReactNodeReactElement
  2. 为什么render类组件的方法返回ReactNode,而函数组件返回ReactElement
  3. 我该如何解决null呢?

Emm*_*fon 60

ReactElement是 React 中元素的类型,可以通过JSXReact.createElement创建。

const a = <div/> // this is a ReactElement
Run Code Online (Sandbox Code Playgroud)

ReactNode更宽,它可以是文本、数字、布尔值、null、未定义、门户、ReactElement 或 ReactNode 数组。它代表React 可以渲染的任何东西

const a = (
  <div>
     hello {[1, "world", true]} // this is a ReactNode
  </div>
)
Run Code Online (Sandbox Code Playgroud)

JSX.Element是 Typescript 的内部钩子。它被设置为等于ReactElement 来告诉 Typescript 每个 JSX 表达式都应该键入为 ReactElements。但如果我们使用 Preact 或其他使用 JSX 的技术,它将被设置为其他内容。

函数式组件返回ReactElement | null,因此它不能返回裸字符串或 ReactElements 数组。这是一个已知的 限制。解决方法是使用Fragments

const Foo = () => {
  return <>hello world!</>  // this works
}
Run Code Online (Sandbox Code Playgroud)

类组件的render 函数 return ReactNode,所以应该没有任何问题。


for*_*d04 49

1.) JSX.Element、ReactNode 和 ReactElement 之间有什么区别?

ReactElement 和 JSX.ElementReact.createElement直接调用或通过 JSX 转译调用的结果。它是一个带有type,props和的对象keyJSX.Elementis ReactElement, whichpropstypehave type any,所以它们或多或少是一样的。

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");
Run Code Online (Sandbox Code Playgroud)

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

const Comp: FunctionComponent = props => <div>{props.children}</div> 
// children?: React.ReactNode
Run Code Online (Sandbox Code Playgroud)

它在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...)
Run Code Online (Sandbox Code Playgroud)

您几乎可以将所有内容分配给ReactNode. 我通常更喜欢更强的类型,但可能有一些有效的情况可以使用它。


2.) 为什么类组件的render方法返回ReactNode,而函数组件返回ReactElement?

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

  • TS类组件:返回ReactNoderender()比阵营/ JS更宽容

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

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

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

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number 
  | boolean | null // Note: undefined is invalid
Run Code Online (Sandbox Code Playgroud)

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>;
Run Code Online (Sandbox Code Playgroud)

注意:避免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`
Run Code Online (Sandbox Code Playgroud)

  • 这个答案很棒。不过,我在 2022 年使用的是 React 18。 `ReactFragment` 现在是 `Iterable&lt;ReactNode&gt;`,不再有 `{}` 类型。 (6认同)

Hos*_*rad 17

https://github.com/typescript-cheatsheets/react#useful-react-prop-type-examples

export declare interface AppProps {
  children1: JSX.Element; // bad, doesnt account for arrays
  children2: JSX.Element | JSX.Element[]; // meh, doesn't accept strings
  children3: React.ReactChildren; // despite the name, not at all an appropriate type; it is a utility
  children4: React.ReactChild[]; // better, accepts array children
  children: React.ReactNode; // best, accepts everything (see edge case below)
  functionChildren: (name: string) => React.ReactNode; // recommended function as a child render prop type
  style?: React.CSSProperties; // to pass through style props
  onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target
  //  more info: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
  props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
  props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*lms 10

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

A ReactElement is an object with a type and props.

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}
Run Code Online (Sandbox Code Playgroud)

A ReactNode is a ReactElement, a ReactFragment, a string, a number or an array of ReactNodes, or null, or undefined, or a boolean:

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

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

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
Run Code Online (Sandbox Code Playgroud)

JSX.Element is a ReactElement, with the generic type for props and type being any. It exists, as various libraries can implement JSX in their own way, therefore JSX is a global namespace that then gets set by the library, React sets it like this:

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}
Run Code Online (Sandbox Code Playgroud)

By example:

 <p> // <- ReactElement = JSX.Element
   <Custom> // <- ReactElement = JSX.Element
     {true && "test"} // <- ReactNode
  </Custom>
 </p>
Run Code Online (Sandbox Code Playgroud)

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

They don't. ReactComponent is defined as:

 render(): JSX.Element | null | false;
Run Code Online (Sandbox Code Playgroud)

And functions are "stateless components":

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

As you can see, they can have ReactNodes as children, but they must return a ReactElement or null.

How do I solve this with respect to null?

Type it as ReactElement | null just as react does. Or let Typescript infer the type.

source for the types

  • @goloRoden 当然,我现在有点累了,需要一些时间来滚动移动设备上的类型......;) (2认同)
  • 伙计们,网络上有关于 React &amp;&amp; TypeScript 的官方文档吗? (2认同)

小智 9

让我们逐步了解 JSX.Element、React.ReactElement、React.ReactNode:

JSX.ElementReact.ReactElement

JSX.Element并且React.ReactElement在功能上是相同的类型。它们可以互换使用。

考虑下面的例子:

const element = <div className="greeting">Hello, world!</div>;
Run Code Online (Sandbox Code Playgroud)

当上面的 JSX 代码运行时,它不会直接创建您可以在屏幕上看到的内容。相反,它创建一个描述该 UI 部分的 JavaScript 对象。这个对象就像一个蓝图或一个食谱。这个蓝图或配方对象如下所示:

const element = {
  type: 'div',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};
Run Code Online (Sandbox Code Playgroud)
  1. type:这告诉 React 我们想要创建什么类型的元素。在本例中,它是一个div.
  2. props:这是一个对象,包含我们传递给元素的所有属性(或“props”)。在这里,我们传递了一个className值为 的 prop 'greeting'
  3. Children:这是一个特殊的道具,代表我们元素内的内容。在本例中,它是字符串'Hello, world!'

React 使用该对象来了解如何构建和更新屏幕上的实际 UI。当 React 看到这个对象时,它知道创建一个 div 元素,给它一个“greeting”类,并放置文本“Hello, world!” 在里面。

简而言之,我们说 ReactElement 是一个具有类型和属性的对象。

JSX.Element那么和之间有什么区别React.ReactElement

JSX.Element是 JSX 表达式的 TypeScript 类型,React.ReactElement是 React 元素的 React 类型。

它是否正确?

const Component = ({
  children,
}: {
  children: JSX.Element;
}) => {
  return <div>{children}</div>;
};


// Usage

<Component>hello world</Component>
Run Code Online (Sandbox Code Playgroud)

在上面提到的示例中,TypeScript 不满意,因为我们将 的类型声明childrenJSX.Element,但是,我们传递的字符串(“hello world”)不是JSX.Element。因此,我们需要一种不同的类型来children接受字符串、数字、null、ReactElement ...。

这就是React.ReactNode闪光的地方。

React.ReactNode

根据下面的定义React.ReactNode

React.ReactNode是 ReactElement、字符串、数字、ReactFragment、ReactPortal、布尔值、null 和未定义。

declare namespace React {
  type ReactNode =
    | ReactElement
    | string
    | number
    | ReactFragment
    | ReactPortal
    | boolean
    | null
    | undefined;
}
Run Code Online (Sandbox Code Playgroud)

例子:

const Component = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return <div>{children}</div>;
};


// Usage
<Component> Hello World </Component>
Run Code Online (Sandbox Code Playgroud)

现在 TypeScript 很高兴!

快乐编码!