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


166

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

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

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

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

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

1
我很惊讶这会出现,因为通常不需要使用组件指定返回类型。您正在为组件执行什么类型的签名?class Example extends Component<ExampleProps> {对于类和const Example: FunctionComponent<ExampleProps> = (props) => {函数组件,应该看起来像(ExampleProps期望道具的接口在哪里)。然后,这些类型具有足够的信息,可以推断出返回类型。
尼古拉斯大厦

这些类型在此处
Jonas Wilms

1
@NicholasTower我们的整理规则强制执行显式提供返回类型的原因,这就是为什么会出现这种情况(恕我直言,这是一件好事,因为与您只是让编译器推断所有内容相比,您对自己所做的事情有更多的思考,这有助于理解) 。
Golo Roden

公平地说,我没有想到掉毛规则。
尼古拉斯大厦

@JonasWilms感谢您的链接,但是我认为这不能回答我的问题。
Golo Roden

Answers:


179

JSX.Element,ReactNode和ReactElement有什么区别?

ReactElement是具有类型和属性的对象。

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

ReactNode是ReactElement,ReactFragment,字符串,ReactNodes的数字或数组,或者为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的泛型类型是任意类型。它存在,因为各种库都可以以自己的方式实现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?

确实,他们确实返回了不同的东西。Component返回:

 render(): ReactNode;

函数是“无状态组件”:

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

这实际上是由于历史原因

我该如何解决null问题?

键入它ReactElement | null一样不作出反应。或者让Typescript推断类型。

类型的来源


感谢您的详细回答!这完美地回答了问题1,但仍未回答问题2和3。您能否也为他们提供一些指导?
Golo Roden

@goloRoden当然,我现在有点累,并且需要一些时间才能浏览移动设备上的类型...;)
Jonas Wilms

嗨@JonasWilms。关于“它们不是。ReactComponent定义为:render():JSX.Element | null | false;”,您在哪里看到的?看起来它向我返回了ReactNode(github.com/DefinitelyTyped/DefinitelyTyped/blob/…)还有一些小错字,我认为ReactComponent应该是React.ComponentComponent
Mark Doliner '19

@MarkDoliner Weird,我可以保证我从文件中复制了该类型...无论如何,我完全会编辑
Jonas Wilms

伙计们在网上某处有任何关于react &&打字稿的正式文件吗?
Goran_Ilic_Ilke

30

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

ReactElement和JSX.ElementReact.createElement直接调用或通过JSX翻译进行调用的结果。这是一个对象typepropskeyJSX.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 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核心不相关的TS类型不兼容。

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

  • TS函数组件:return JSX.Element | null,比React / JS更具限制性

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

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

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.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`
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.