Skip to content

React 18 中处理 TypeScript 子组件类型

问题描述

在升级到 React 18 后,许多开发者发现原本正常工作的组件开始抛出 TypeScript 错误:Property 'children' does not exist on type 'IPageProps'

这个问题的根本原因是 React 18 的 TypeScript 类型定义发生了变化。在之前的版本中,React.FC(函数组件)类型会自动包含 children 属性,但在 React 18 中这个隐式包含被移除了,需要开发者显式声明。

解决方案

以下是几种处理 React 18 中子组件类型的最佳实践方法:

方法一:手动添加 children 属性

最简单的解决方案是在你的 props 接口中显式声明 children 属性:

typescript
import React from 'react';

interface Props {
  title: string;
  // 显式声明 children 为可选属性
  children?: React.ReactNode;
}

const Component: React.FC<Props> = ({ title, children }) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
};

方法二:使用 React.PropsWithChildren 工具类型

React 提供了 PropsWithChildren 工具类型,可以更方便地处理子组件:

typescript
import React from 'react';

interface Props {
  title: string;
}

// 方式1:直接使用 PropsWithChildren 包装
const Component: React.FC<React.PropsWithChildren<Props>> = ({ title, children }) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
};

// 方式2:扩展接口
interface PropsWithChildren extends React.PropsWithChildren {
  title: string;
}

const AnotherComponent: React.FC<PropsWithChildren> = ({ title, children }) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
};

方法三:创建自定义类型别名

如果你在多个组件中都需要处理子组件,可以创建自定义类型别名:

typescript
// types/react.d.ts 或全局类型文件
import { FC, PropsWithChildren } from 'react';

// 自定义类型,自动包含 children
export type FCC<P = {}> = FC<PropsWithChildren<P>>;

然后在组件中使用:

typescript
import { FCC } from '../types/react';

interface Props {
  title: string;
}

const Component: FCC<Props> = ({ title, children }) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
};

方法四:放弃 React.FC,使用普通函数

许多开发者建议完全放弃 React.FC 类型,使用普通函数语法:

typescript
import React from 'react';

interface Props {
  title: string;
  children?: React.ReactNode;
}

// 使用函数声明而不是 React.FC
function Component({ title, children }: Props) {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
}

// 或者使用箭头函数
const Component = ({ title, children }: Props) => {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  );
};

为什么推荐这种方法?

  • 更简洁的语法
  • 更明确的类型推断
  • 避免 React.FC 的一些固有约束
  • 社区趋势逐渐远离 React.FC

解决原理

React 团队做出这一改变的主要原因是为了提高类型安全性。在旧版本中,即使组件实际上并不使用 childrenReact.FC 也会自动包含它,这可能导致以下问题:

typescript
// 这个组件实际上并不接受或使用 children
const ComponentWithNoChildren: React.FC = () => <div>Hello</div>;

// 但类型上不会报错,尽管实际上 children 会被忽略
<ComponentWithNoChildren>
  <SomeChild /> {/* 这个子组件实际上不会被渲染 */}
</ComponentWithNoChildren>

通过要求显式声明 children,TypeScript 现在能够更准确地反映组件的实际行为。

迁移建议

  1. 逐步迁移:不需要一次性修改所有组件,可以按需逐步修改
  2. 选择一致的策略:在项目中统一使用上述某一种方法
  3. 代码检查:配置 ESLint 规则来帮助保持一致性

注意

虽然可以通过类型覆盖恢复旧行为,但不推荐这样做,因为这违背了 React 团队改进类型安全性的初衷。

总结

React 18 中移除 React.FC 隐式包含 children 是一个有意为之的破坏性变更,旨在提高类型安全性。通过显式声明 children 属性或使用提供的工具类型,可以轻松适应这一变化。

选择最适合你项目的方法并保持一致性是关键。对于新项目,考虑使用普通函数语法而不是 React.FC,这代表了当前的社区最佳实践。