【问题标题】:When to use JSX.Element vs ReactNode vs ReactElement?何时使用 JSX.Element、ReactNode 和 ReactElement?
【发布时间】:2021-08-30 23:00:17
【问题描述】:

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

我一直使用JSX.Element作为返回类型,现在如果组件决定渲染任何东西,这将不再起作用,即返回null,因为nullJSX.Element 的值无效。这是我旅程的开始。我在网上搜索,发现您应该改用ReactNode,其中包括null 和其他一些可能发生的事情。

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

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

  1. JSX.ElementReactNodeReactElement 有什么区别?
  2. 为什么类组件的render方法返回ReactNode,而函数组件返回ReactElement
  3. 对于null,我该如何解决这个问题?

【问题讨论】:

  • 我很惊讶会出现这种情况,因为通常您不需要使用组件指定返回类型。你为组件做什么类型的签名?对于类,应该类似于 class Example extends Component<ExampleProps> {,对于函数组件应该类似于 const Example: FunctionComponent<ExampleProps> = (props) => {(其中 ExampleProps 是预期道具的接口)。然后这些类型有足够的信息可以推断出返回类型。
  • 类型定义here
  • @NicholasTower 我们的 linting 规则强制明确提供返回类型,这就是出现这种情况的原因(恕我直言,这是一件好事,因为您对自己所做的事情的思考更多,这有助于理解,而不是如果你只是让编译器推断一切)。
  • 很公平,我没有想到 linting 规则。
  • @JonasWilms 感谢您的链接,但我认为这不能回答我的问题。

标签: javascript reactjs typescript


【解决方案1】:

JSX.Element、ReactNode 和 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、一个字符串、一个数字或一个 ReactNode 数组,或者为 null,或者未定义,或者一个布尔值:

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

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

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

JSX.Element 是一个 ReactElement,props 和 type 的泛型类型是 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>

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

确实,它们确实返回了不同的东西。 Components 回复:

 render(): ReactNode;

而函数是“无状态组件”:

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

这实际上是由于historical reasons

关于 null 我该如何解决这个问题?

就像 react 一样,输入 ReactElement | null。或者让 Typescript 推断类型。

source for the types

【讨论】:

  • 感谢您的详细解答!这完美地回答了问题 1,但仍然没有回答问题 2 和 3。请您也提供一些关于它们的指导吗?
  • @goloRoden 当然,我现在有点累,在手机上滚动浏览类型需要一些时间...... ;)
  • 嗨@JonasWilms。关于“他们没有。ReactComponent 被定义为:render(): JSX.Element | null | false;”,你在哪里看到的?看起来它向我返回了 ReactNode (github.com/DefinitelyTyped/DefinitelyTyped/blob/…) 也是轻微的错字,我认为 ReactComponent 应该是 React.ComponentComponent
  • @MarkDoliner Weird,我可以发誓我从文件中复制了那个类型......无论如何,你完全正确,我会编辑
  • 伙计们,网络上有没有关于 react && typescript 的官方文档?
【解决方案2】:

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

ReactElement 和 JSX.Element 是直接调用 React.createElement 或通过 JSX 转译的结果。它是一个带有typepropskey 的对象。 JSX.ElementReactElement,其propstype 的类型为any,所以它们或多或少是相同的。

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

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

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

React type declarations 中看起来更复杂,但等价于到:

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

tl;dr:这是一个当前的TS类型不兼容not related to core React

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

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

原则上,React/JS 类组件中的render()supports the same return types 为函数组件。对于 TS 来说,不同的类型是由于历史原因和向后兼容的需要而仍然存在的类型不一致。

理想情况下,valid return type 可能看起来更像这样:

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

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

注意:避免React.FCwon't save你来自JSX.Element | null返回类型限制。

最近从其模板创建 React App dropped 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`

【讨论】:

    猜你喜欢
    • 2021-05-05
    • 2022-11-18
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    • 2017-11-06
    • 1970-01-01
    • 2021-04-24
    相关资源
    最近更新 更多