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

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

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

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

JSX和JSX有什么区别。元素,ReactNode和ReactElement? 为什么类组件的呈现方法返回ReactNode,而功能组件返回ReactElement? 我怎么解这个关于null的函数?


JSX和JSX有什么区别。元素,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,一个字符串,一个数字或一个ReactNodes数组,或null,或undefined,或一个布尔值:

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的泛型类型是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>

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

事实上,它们确实返回不同的东西。组件返回:

 render(): ReactNode;

函数是“无状态组件”:

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

这其实是有历史原因的。

我怎么解这个关于null的函数?

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

类型的源代码


1)。JSX和JSX有什么区别。元素,ReactNode和ReactElement?

ReactElement和JSX。元素 是调用React的结果。直接或通过JSX编译createElement。它是一个具有类型、道具和键的对象。JSX。元素是ReactElement,其道具和类型有类型any,因此它们或多或少是相同的。

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

ReactNode用作类组件中render()的返回类型。它也是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)。为什么类组件的呈现方法返回ReactNode,而函数组件返回ReactElement?

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

TS类组件:返回带有render()的ReactNode,比React/JS更宽容 TS函数组件:返回JSX。元素|为空,比React/JS更严格

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

理想情况下,一个有效的返回类型应该是这样的:

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

3)。我怎么解这个关于null的函数?

Some options:
// 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中拯救出来。元素|空返回类型限制。

Create React App最近发布了React。FC从它的模板,因为它有一些怪癖,如隐式{children?: ReactNode}类型定义。使用React。有节制地使用FC可能更可取。

In edge cases, you can add a type assertion or Fragments as workaround:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`

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
}

ReactElement是React中元素的类型,可以通过JSX创建,也可以通过React. createelement创建。

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

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

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

JSX。元素是Typescript的内部钩子。它被设置为ReactElement,以告诉Typescript每个JSX表达式都应该被类型化为ReactElement。但如果我们使用Preact或其他使用JSX的技术,它将被设置为其他东西。

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

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

类组件的渲染函数返回ReactNode,所以应该没有任何问题。