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
属性:
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
工具类型,可以更方便地处理子组件:
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>
);
};
方法三:创建自定义类型别名
如果你在多个组件中都需要处理子组件,可以创建自定义类型别名:
// types/react.d.ts 或全局类型文件
import { FC, PropsWithChildren } from 'react';
// 自定义类型,自动包含 children
export type FCC<P = {}> = FC<PropsWithChildren<P>>;
然后在组件中使用:
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
类型,使用普通函数语法:
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 团队做出这一改变的主要原因是为了提高类型安全性。在旧版本中,即使组件实际上并不使用 children
,React.FC
也会自动包含它,这可能导致以下问题:
// 这个组件实际上并不接受或使用 children
const ComponentWithNoChildren: React.FC = () => <div>Hello</div>;
// 但类型上不会报错,尽管实际上 children 会被忽略
<ComponentWithNoChildren>
<SomeChild /> {/* 这个子组件实际上不会被渲染 */}
</ComponentWithNoChildren>
通过要求显式声明 children
,TypeScript 现在能够更准确地反映组件的实际行为。
迁移建议
- 逐步迁移:不需要一次性修改所有组件,可以按需逐步修改
- 选择一致的策略:在项目中统一使用上述某一种方法
- 代码检查:配置 ESLint 规则来帮助保持一致性
注意
虽然可以通过类型覆盖恢复旧行为,但不推荐这样做,因为这违背了 React 团队改进类型安全性的初衷。
总结
React 18 中移除 React.FC
隐式包含 children
是一个有意为之的破坏性变更,旨在提高类型安全性。通过显式声明 children
属性或使用提供的工具类型,可以轻松适应这一变化。
选择最适合你项目的方法并保持一致性是关键。对于新项目,考虑使用普通函数语法而不是 React.FC
,这代表了当前的社区最佳实践。