我目前正在将 React 应用程序迁移到 TypeScript。到目前为止,这工作得很好,但是我的 render
函数的返回类型存在问题,特别是在我的函数组件中。
我一直使用 JSX.Element
作为返回类型,现在如果组件决定不 呈现任何内容,即返回 null
,这将不再起作用,因为 null
不是有效的JSX.Element
的值。这是我旅程的开始。我在网上搜索,发现您应该改用 ReactNode
,其中包括 null
和一些其他可能发生的事情。
但是,在创建函数式组件时,TypeScript 会抱怨 ReactNode
类型。同样,经过一番搜索,我发现,对于功能组件,您应该使用 ReactElement
代替。但是,如果我这样做,兼容性问题就消失了,但现在 TypeScript 再次抱怨 null
不是有效值。
长话短说,我有三个问题:
JSX.Element、ReactNode 和 ReactElement 有什么区别?为什么类组件的render方法返回ReactNode,而功能组件返回ReactElement?关于null,我该如何解决这个问题?
class Example extends Component<ExampleProps> {
,对于函数组件应该类似于 const Example: FunctionComponent<ExampleProps> = (props) => {
(其中 ExampleProps
是预期道具的接口)。然后这些类型有足够的信息可以推断出返回类型。
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,或者 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>
为什么类组件的render方法返回ReactNode,而函数组件返回ReactElement?
事实上,它们确实返回了不同的东西。 Component
的回报:
render(): ReactNode;
函数是“无状态组件”:
interface StatelessComponent<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement | null;
// ... doesn't matter
}
这实际上是由于 historical reasons。
关于null,我该如何解决这个问题?
就像 react 一样,将其键入为 ReactElement | null
。或者让 Typescript 推断类型。
1.) JSX.Element、ReactNode 和 ReactElement 有什么区别?
ReactElement 和 JSX.Element 是直接或通过 JSX 转换调用 React.createElement
的结果。它是一个包含 type
、props
和 key
的对象。 JSX.Element
是 ReactElement
,其 props
和 type
具有类型 any
,因此它们或多或少相同。
const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");
ReactNode 用作类组件中 render()
的返回类型。它也是具有 PropsWithChildren
的 children
属性的默认类型。
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类组件:用render()返回ReactNode,比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.FC
won'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`
ReactFragment
现在是 Iterable<ReactNode>
,不再有类型 {}
。
ReactElement 是 React 中元素的类型,通过 JSX 或 React.createElement 创建。
const a = <div/> // this is a ReactElement
ReactNode 更广泛,它可以是文本、数字、布尔值、null、未定义、门户、ReactElement 或 ReactNode 数组。它代表 React 可以渲染的任何东西。
const a = (
<div>
hello {[1, "world", true]} // this is a ReactNode
</div>
)
JSX.Element
是 Typescript 的内部挂钩。 设置等于 ReactElement 告诉 Typescript 每个 JSX 表达式都应该被输入为 ReactElements。但是如果我们使用 Preact,或者其他使用 JSX 的技术,它就会被设置为其他东西。
功能组件返回 ReactElement | null
,因此它不能返回裸字符串或 ReactElements 数组。它是一个 known limitation。解决方法是使用 Fragments :
const Foo: FC = () => {
return <>hello world!</> // this works
}
类组件的渲染函数返回ReactNode
,所以应该没有问题。
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
}
不定期副业成功案例分享
ReactComponent
应该是React.Component
或Component
。