【问题标题】:React 18 TypeScript children FCReact 18 TypeScript 儿童 FC
【发布时间】:2022-07-06 14:45:25
【问题描述】:

我升级到 React 18 并且编译正常。今天,似乎每个使用子组件的组件都在抛出错误。 Property 'children' does not exist on type 'IPageProps'.

在儿童道具自动包含在FC 界面中之前。现在看来我必须手动添加children: ReactNode。 What is the correct typescript type for react children?

这是 React 18 更新的一部分,还是我的环境搞砸了?

package.json

"react": "^18.0.0",
"react-dom": "^18.0.0",
"next": "12.1.4",
"@types/react": "18.0.0",
"@types/react-dom": "18.0.0",

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "alwaysStrict": true,
    "sourceMap": true,
    "incremental": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

【问题讨论】:

  • 我遇到了同样的问题。看起来,无论出于何种原因,他们在 Component 类上删除了children

标签: javascript reactjs typescript


【解决方案1】:

虽然this answer 是正确的,但我想指出您绝对不必必须使用这个PropsWithChildren 助手。 (它主要用于codemod,而不是手动使用。)

相反,我发现手动定义它们更容易。

之前

import * as React from 'react';

type Props = {};
const Component: React.FC<Props> = ({children}) => {...}

之后

import * as React from 'react';

type Props = {
  children?: React.ReactNode
};
const Component: React.FC<Props> = ({children}) => {...}

这就是我们所需要的。

或者您可以完全停止使用React.FC。

import * as React from 'react';

type Props = {
  children?: React.ReactNode
};

function Component({children}: Props): React.ReactNode {
  ...
}

在 React 中,children 是一个常规属性,并不是什么特别的东西。所以你需要像定义所有其他道具一样定义它。之前隐藏它的类型是错误的。

【讨论】:

  • 我知道这有点离题,但你认为ref 也将成为“常规道具”吗?键入ref,尤其是对于包装其他组件的组件,通常非常困难。如果ref 是普通道具,React.ComponentProps 会解决它。
  • 在某些时候,我认为是这样——在我们能够做到这一点之前,我们需要做出一些改变。我不明白为什么打字会很复杂。这可能是一个单独的讨论,欢迎您在 DefinedTyped 存储库中开始讨论。
  • 很高兴听到这个消息。该问题与为设计系统库键入 forwardRef 有关。对于const Box = styled(View),我不确定如何使Box 具有与View 相同的引用类型,而对于道具来说,这很容易使用泛型。不过,我将在 DefinitiveTyped 存储库中继续此操作,感谢您的及时回复!
  • 感谢您的帮助/回答。在哪里可以使代码中的这种更改像以前那样工作
  • VFC 呢?这一切看起来都很简单:FC 添加了隐含的children,VFC 没有。现在在 React 18 中似乎两者都是一样的,因为......?看来我将创建一个新的 type CFC&lt;T&gt; = FC&lt;PropsWithChildren&lt;T&gt;&gt; 来恢复这种区别。
【解决方案2】:

如何解决

没有道具

之前

import React from 'react';

const Component: React.FC = ({children}) => {...}

之后

创建例如react.d.ts 来定义你的助手类型 1

export type PropsWithChildrenOnly = PropsWithChildren<unknown>;
export type ReactFCWithChildren = React.FC<PropsWithChildrenOnly>;
import React from 'react';
import { PropsWithChildrenOnly, ReactFCWithChildren } from './react';

const Component: React.FC<PropsWithChildrenOnly> = ({children}) => {...}

或

const Component: ReactFCWithChildren = ({children}) => {...}

带道具

之前

import React from 'react';

interface Props {
  ...
}
const Component: React.FC<Props> = ({children}) => {...}

之后

import React, { PropsWithChildren } from 'react';

interface Props {
  ...
}
const Component: React.FC<PropsWithChildren<Props>> = ({children}) => {...}

或

interface Props extends PropsWithChildren<unknown> {
  ...
}

const Component: React.FC<Props> = ({children}) => {...}

1虽然defining children manually 看起来很简单,但最好利用@types 包中已经为您准备好的类型。当将来类型发生变化时,它会自动从代码中的任何地方传播,这样你就不必自己去碰它了。

出于某种原因禁止警告

您可以通过使用以下定义创建 react.d.ts 文件来覆盖反应类型,这会将类型恢复为 @types/react v17

import * as React from '@types/react';

declare module 'react' {
  interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  }
}

FC 签名为什么会改变

children 属性已从 React.FunctionComponent (React.FC) 中删除,因此您必须明确声明它。

TS 会告诉你类似的错误

类型'{孩子:...; }' 与类型 'IntrinsicAttributes' 没有共同的属性。”

You can read why here。 TLDR 它可以防止类似的错误

const ComponentWithNoChildren: React.FC = () => <>Hello</>;

...

<ComponentWithNoChildren>
   // passing children is wrong since component does not accept any
   <UnusedChildrenSinceComponentHasNoChildren /> 
</ComponentWithNoChildren>

【讨论】:

  • 第一个选项有ts错误Generic type 'PropsWithChildren' requires 1 type argument(s).,但是第二个就完美了
  • @LaszloSarvold 我已经反映了你的评论,谢谢。
  • 这很好,第三方库呢?我一直在破解它const ThirdPartyHack: any = ThirdParty
  • @MichaelJosephAubry 不幸的是,您将不得不等待图书馆适应这一点(我的理想答案是:D)
  • 请注意,您不必使用这个 PropsWithChildren 助手。您可以像任何其他道具一样手动定义孩子。我会说这是一种更清晰、更简单的方法,因为它不会引入新概念。 stackoverflow.com/a/71809927/458193
【解决方案3】:

为您创建自定义功能组件类型FC。

让我们把它命名为FCC

// Put this in your global types.ts

import { FC, PropsWithChildren } from "react";

// Custom Type for a React functional component with props AND CHILDREN
export type FCC<P={}> = FC<PropsWithChildren<P>>

只要你想在组件的props 中使用children 属性,就可以像这样使用它:

// import FCC from types.ts
const MyComponent: FCC = ({children}) => {
  return (
    <>{children}</>
  )
}

或

interface MyCompoProps{
  prop1: string
}

const MyComponent: FCC<MyCompoProps> = ({children, prop1}) => {
  return (
    <>{children}</>
  )
}

PS此答案可能类似于@Garvae 的answer,但他的ReactFCC&lt;P&gt; type 应该写成ReactFCC&lt;P={}&gt; 以防止出现以下错误:

Generic type 'ReactFCC' requires 1 type argument(s)

当您没有向组件传递任何道具时会发生此错误。 children 道具应该是可选道具。 所以给这些道具一个默认的{}值(即P = {})可以解决这个问题。

【讨论】:

    【解决方案4】:

    是的,React.FC 类型已更改。但是你可以用二十一点声明你自己的类型并反应孩子。

    我的方法是创建 src/types/react.d.ts,内容如下:

    import React, { PropsWithChildren } from 'react';
    
    export type ReactFCC<T> = React.FC<PropsWithChildren<T>>;
    

    【讨论】:

      【解决方案5】:

      It looks like the children attribute on the typescript typings were removed.

      我不得不手动将孩子添加到我的道具中;可能有更好的解决方案来解决此问题,但在此期间,这可行。

      【讨论】:

        【解决方案6】:

        正如 Dan 在他的回答中指出的那样,您可能不再需要 React.FC。如果您选择使用普通的function,这里有一个额外的建议。

        没有子组件的组件

        import * as React from 'react';
        
        type Props = {
        };
        
        export function Component({}: Props) {
          ...
        }
        
        <Component /> // Valid
        <Component>test</Component> // Invalid
        

        需要包含子组件的组件

        import * as React from 'react';
        
        type Props = {
          children: React.ReactNode
        };
        
        export function Component({children}: Props) {
          ...
        }
        
        <Component>test</Component> // Valid
        <Component /> // Invalid
        

        可选的子组件

        import * as React from 'react';
        
        type Props = {
          children?: React.ReactNode
        };
        
        export function Component({children}: Props) {
          ...
        }
        
        <Component>test</Component> // Valid
        <Component /> // Valid
        

        使用 React.ReactNode 作为返回类型似乎不是一个好的建议,因为从另一个组件的顶部渲染这样的组件会导致:[tsserver 2786] [E] 'Example' cannot be used as a JSX component。它的返回类型“ReactNode”不是有效的 JSX 元素。类型“未定义”不可分配给类型“元素 |无效的'。试试这个: function Example (): React.ReactNode { return };函数其他():React.ReactNode {返回}

        完全省略返回类型可能会更好。只要有代码尝试使用该组件,tsserver 就会自动警告无效返回。函数示例 () {};您可以像这样将用法放在同一个文件中,然后如果无效,tsserver 会发出警告。您甚至可以跳过分配给变量的步骤。

        【讨论】:

          【解决方案7】:

          首先,你必须定义一个全局接口

          import { PropsWithChildren } from "react";
          
          interface ReactFC<T = {}> extends React.FC<PropsWithChildren<T>> {}
          

          组件的道具

              interface ReceiptProps {
                 onSubmitForm: () => void;
                 orderId: number;
              } 
          
              const Receipt: ReactFC<ReceiptProps> = ({orderId, onSubmitForm,children }) => {
                  return <div> { children } </div>
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-02-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-07-11
            • 1970-01-01
            相关资源
            最近更新 更多