当子节点可以是 React 节点或函数时如何使用 React.FC<props> 类型

Dim*_*nis 6 javascript typescript reactjs react-typescript

我有这个示例组件

import React, { FC, ReactNode, useMemo } from "react";
import PropTypes from "prop-types";

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props) {
  const val = useMemo(() => {
    return 1;
  }, []);

  return (
    <div>
      {typeof props.children === "function"
        ? props.children(val)
        : props.children}
    </div>
  );
};

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

export default Comp;
Run Code Online (Sandbox Code Playgroud)

我的意图是children组件的道具可以是

  • a node,它被描述为

    任何可以渲染的东西:数字、字符串、元素或包含这些类型的数组(或片段)。

  • a function, (或“渲染道具”),它只是从组件内部获取一个值并返回另一个node

这里的重点是明确的,children可以是一个 ( node,这几乎是一切) 或另一个 ( 这只是一个function)

问题

但是,我在类型检查方面面临以下问题。

  • 如果我保留此处显示的代码,则会在行中收到以下错误消息 ? props.children(val)

    此表达式不可调用。并非所有类型为“功能 | ”的成分 ((x: number) => ReactNode) | (字符串 & {}) | (数字和{})| (false & {}) | (真 & {}) | ({} & 字符串) | ({} & 数字) | ({} & 假) | ({} & 真) | (((x: number) => ReactNode) & 字符串)

我不明白这个错误。

  • 如果我将Props类型更改为
type Props = {
  children: (x: number) => ReactNode;
};
Run Code Online (Sandbox Code Playgroud)

并依靠 React 自己type PropsWithChildren<P> = P & { children?: ReactNode };来处理children不是函数的情况,然后我得到错误

(property) children?: PropTypes.Validator<(x: number) => React.ReactNode> Type 'Validator' 不能分配给 type 'Validator<(x: number) => ReactNode>'。类型 'ReactNodeLike' 不能分配给类型 '(x: number) => ReactNode'。类型 'string' 不可分配给类型 '(x: number) => ReactNode'.ts(2322) Comp.tsx(5, 3):预期的类型来自属性 'children',它在类型上声明

在线上 children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired

唯一的解决方案是将Props类型保留为

type Props = {
  children: (x: number) => ReactNode;
};
Run Code Online (Sandbox Code Playgroud)

并且还将 the 更改Comp.propTypeschildren: PropTypes.func.isRequired,这不是我想要的,因为我想明确表示。

问题

我怎样才能保持代码明确,如本问题开头所示,并且没有类型检查抛出错误?

代码沙盒链接

for*_*d04 6

域名:

React.FC类型是针对病因上述错误

  1. 它已经包含默认children类型为ReactNode,它&children包含在Props.
  2. ReactNode是一种相当宽的类型,限制了编译器将children联合类型缩小到与第 1 点相结合的可调用函数的能力。

一个解决方案是省略FC和使用更窄的类型,而不是ReactNode有利于类型安全:

type Renderable = number | string | ReactElement | Renderable[]
type Props = {
  children: ((x: number) => Renderable) | Renderable;
};
Run Code Online (Sandbox Code Playgroud)

更多细节

首先,这里是内置的 React 类型:

type Renderable = number | string | ReactElement | Renderable[]
type Props = {
  children: ((x: number) => Renderable) | Renderable;
};
Run Code Online (Sandbox Code Playgroud)

1.) 你FC<Props>用来输入Comp. FC内部已经包含一个children类型为 as的声明ReactNode,它与children来自的定义合并Props

type Props = { children: ((x: number) => ReactNode) | ReactNode } & 
  { children?: ReactNode }
// this is how the actual/effective props rather look like
Run Code Online (Sandbox Code Playgroud)

2.) 查看ReactNode类型,您会发现类型变得相当复杂。ReactNode包括类型{}via ReactFragment,它是nulland之外的所有内容undefined超类型。我不知道这种类型形状背后的确切决定,microsoft/TypeScript#21699暗示了历史和向后兼容的原因。

因此,children类型比预期的要宽。这会导致您的原始错误:类型保护typeof props.children === "function"无法将类型“混乱”正确缩小到function了。

解决方案

忽略 React.FC

最后,React.FC它只是一个具有额外属性的函数类型,如propTypes,displayName等,具有自以为是的宽children类型。省略FC此处将为编译器和 IDE 显示提供更安全、更易于理解的类型。如果我把你定义Anything that can be renderedchildren,这可能是:

import React, { ReactChild } from "react";
// You could keep `ReactNode`, though we can do better with more narrow types
type Renderable = ReactChild | Renderable[]

type Props = {
  children: ((x: number) => Renderable) | Renderable;
};

const Comp = (props: Props) => {...} // leave out `FC` type
Run Code Online (Sandbox Code Playgroud)

自定义FC类型不children

您可以定义自己的FC版本,其中包含React.FC除这些宽children类型之外的所有内容:

type FC_NoChildren<P = {}> = { [K in keyof FC<P>]: FC<P>[K] } & // propTypes etc.
{ (props: P, context?: any): ReactElement | null } // changed call signature

const Comp: FC_NoChildren<Props> = props => ...
Run Code Online (Sandbox Code Playgroud)

游乐场示例