乐闻世界logo
搜索文章和话题

如何使用 JSX. Element 、 ReactNode 、 ReactElement ?

9 个月前提问
5 个月前修改
浏览次数188

6个答案

1
2
3
4
5
6

当我们在React项目中开发用户界面时,我们经常会用到几个核心概念,包括JSX Element、ReactNode和ReactElement。现在我将逐一解释这些概念,并提供使用的场景。

JSX Element

JSX (JavaScript XML) 是一个React语法扩展,它允许我们在JavaScript代码中写类似HTML的标记。每当我们编写诸如 <div>Hello World</div> 这样的代码时,我们实际上创建了一个JSX Element。

使用场景:

  • 直接在组件中编写UI: 最直接的使用场景就是在函数组件或类组件中返回UI布局时。
    jsx
    function Welcome() { return <div>Welcome to React</div>; }
  • 条件渲染: 在需要根据条件判断来显示不同的UI元素时,通常会使用JSX Elements。
    jsx
    function Greeting({ isUserLoggedIn }) { return isUserLoggedIn ? <div>Welcome back!</div> : <div>Please sign in.</div>; }

ReactNode

ReactNode 是React类型定义中的一个类型,它可以是字符串或数字类型的文本,JSX元素,JSX Fragment,null 或者 undefined,甚至是这些类型的数组。ReactNode 更多是用于类型定义,确保组件可以处理各种类型的子组件或值。

使用场景:

  • 作为props或子组件: 当编写可复用组件时,我们可以将其子元素类型指定为ReactNode,以接受多种类型的子元素。
    jsx
    type CardProps = { children: React.ReactNode; }; function Card({ children }: CardProps) { return <div className="card">{children}</div>; }
  • 渲染动态内容: 在渲染不确定类型的内容时,使用ReactNode类型可以使得组件更加灵活。
    jsx
    const content: React.ReactNode = getContent(); // getContent可能返回字符串、元素或null return <div>{content}</div>;

ReactElement

ReactElement 是对JSX Element的一种抽象,它们是由React.createElement()函数创建的对象。当JSX编译之后,每个JSX元素实际上都会变成一个ReactElement

使用场景:

  • 使用createElement创建元素: 当我们需要在不使用JSX语法的环境中创建元素时,可以使用React.createElement
    jsx
    function Button() { return React.createElement('button', null, 'Click me'); }
  • 类型定义: 当我们需要明确一个变量或一个函数返回值应该是React元素时。
    tsx
    function getHeaderElement(): React.ReactElement { return <h1>Header</h1>; }

总结来说:

  • JSX Element 是我们编写的类HTML代码,用于声明式地描述UI。
  • ReactNode 是一个类型定义,涵盖了可以渲染的几乎所有类型的内容。
  • ReactElement 是由React.createElement创建的对象,它是JSX元素的底层表示形式。

开发者会根据具体的应用场景,结合TypeScript或PropTypes的类型系统,来决定何时使用它们。这些类型的使用有助于确保组件的可复用性、可维护性,以及在不同的使用场景中保持类型的一致性。

2024年6月29日 12:07 回复

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

A ReactElement is an object with type, props, and key properties:

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

A JSX.Element is a ReactElement<any, any>. It exists as various libraries can implement JSX in their own way:

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

A ReactPortal is a ReactElement with a children property:

shell
interface ReactPortal extends ReactElement { children: ReactNode; }

A ReactNode is a ReactElement, string, number, Iterable<ReactNode>, ReactPortal, boolean, null, or undefined:

shell
type ReactNode = | ReactElement | string | number | Iterable<ReactNode> | ReactPortal | boolean | null | undefined;

Example:

shell
<div> // <- ReactElement <Component> // <- ReactElement {condition && 'text'} // <- ReactNode </Component> </div>

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

This is due to historical reasons.

A Component.render returns a ReactNode:

shell
class Component<P, S> { // … render(): ReactNode; // … }

A FunctionComponent returns a ReactElement<any, any> | null:

shell
interface FunctionComponent<P = {}> { (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; propTypes?: WeakValidationMap<P> | undefined; contextTypes?: ValidationMap<any> | undefined; defaultProps?: Partial<P> | undefined; displayName?: string | undefined; }

How do I solve this with respect to null?

Type it as ReactElement | null just as React does. Or let TypeScript infer the type.

2024年6月29日 12:07 回复

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.

shell
const jsx = <div>hello</div> 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.

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

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

shell
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:

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

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

Some options:

shell
// 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>;

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:

shell
const MyCompFragment: FunctionComponent = () => <>"Hello"</> const MyCompCast: FunctionComponent = () => "Hello" as any // alternative to `as any`: `as unknown as JSX.Element | null`
2024年6月29日 12:07 回复

ReactElement is the type for elements in React, either created via JSX or React.createElement.

shell
const a = <div/> // this is a ReactElement

ReactNode is wider, it can be text, number, boolean, null, undefined, a portal, a ReactElement, or an array of ReactNodes. It represents anything that React can render.

shell
const a = ( <div> hello {[1, "world", true]} // this is a ReactNode </div> )

JSX.Element is an internal hook for Typescript. It is set equal to ReactElement to tell Typescript that every JSX expressions should be typed as ReactElements. But if we'd use Preact, or other technologies using JSX it would be set to something else.

Functional components return ReactElement | null, so it cannot return a bare string or an array of ReactElements. It is a known limitation. The workaround is to use Fragments :

shell
const Foo = () => { return <>hello world!</> // this works }

Class components' render function return ReactNode, so there shouldn't be any problem.

2024年6月29日 12:07 回复

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

shell
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 }
2024年6月29日 12:07 回复

Let's understand JSX.Element vs React.ReactElement vs React.ReactNode step by step:

JSX.Element and React.ReactElement

JSX.Element and React.ReactElement are functionally the same type. They can be used interchangeably.

Consider below example:

shell
const element = <div className="greeting">Hello, world!</div>;

When the above JSX code runs, it doesn't directly create something you can see on the screen. Instead, it creates a JavaScript object that describes that piece of UI. This object is like a blueprint or a recipe. This is how this blueprint or recipe object will look like:

shell
const element = { type: 'div', props: { className: 'greeting', children: 'Hello, world!' } };
  1. type: This tells React what type of element we want to create. In this case, it's a div.
  2. props: This is an object that holds all the properties (or "props") that we've passed to our element. Here, we've passed a className prop with the value 'greeting'.
  3. children: This is a special prop that represents the content inside our element. In this case, it's the string 'Hello, world!'.

This object is what React uses to understand how to build and update the actual UI on the screen. When React sees this object, it knows to create a div element, give it a class of 'greeting', and put the text 'Hello, world!' inside it.

In a nutshell, we say A ReactElement is an object with a type and props.

So what is the difference between JSX.Element and React.ReactElement?

JSX.Element is a TypeScript type for JSX expressions, and React.ReactElement is a React type for React elements.

Is this correct?

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

In above stated example, TypeScript isn't happy because we declared the type of children to be JSX.Element, however, we are passing a string ("hello world") which is not a JSX.Element. Hence, we need a different type for children that can accept string, number, null, ReactElement ....

This is where React.ReactNode shines.

React.ReactNode

As per below definition of React.ReactNode.

React.ReactNode is a ReactElement, string, number, ReactFragment, ReactPortal, boolean, null & undefined.

shell
declare namespace React { type ReactNode = | ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined; }

Example:

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

Now TypeScript is happy!!

Happy coding!

2024年6月29日 12:07 回复

你的答案