1. React.FC是一个函数式组件,是在TypeScript中使用的一个泛型
(很神奇,看了很多篇文章开头的介绍都是这两句)
React.FC 包含了 PropsWithChildren 的泛型,不用显式的声明 props.children 的类型。
这个时候我的疑问就冒出来了
首先,PropsWithChildren是什么?其次,React.FC是怎么包含PropsWithChildren 的泛型的?
让我们逐一解答
1.1 PropsWithChildren 详解
PropsWithChildren 是 React 提供的一个工具类型,用于为组件 Props 自动添加 children 属性。源码长这个样子:
type PropsWithChildren<P> = P & {
children?: React.ReactNode;
};
这里使用了&符号将任意的Props类型P和children属性合并,children是可选属性,类型为React.ReactNode,也就是任意合法的React子元素。
举个简单的属性合并的例子我们就能知道他到底做了什么了:
// 原始 Props 类型
interface UserProps {
name: string;
age: number;
}
// 应用 PropsWithChildren
type UserWithChildren = PropsWithChildren<UserProps>;
/* 合并后等价于:
type UserWithChildren = {
name: string;
age: number;
children?: React.ReactNode;
} */
const props: UserWithChildren = {
name: "Alice",
age: 30,
children: <div>Hello</div> // ✅ 合法
};
const props2: UserWithChildren = {
name: "Bob",
age: 25 // ✅ 合法(children 可选)
};
当原类型P包含children属性时,则会出现属性覆盖
interface ConflictProps {
children: string; // 已定义 children 为 string
}
type Merged = PropsWithChildren<ConflictProps>;
/* 合并后等价于:
type Merged = {
children: string & React.ReactNode; // 类型冲突!
} */
// string 是 React.ReactNode 的子类型(因为 React.ReactNode 包含字符串)最终 children 类型为 string(更具体的类型胜出)但此时 children 变为可选属性(因为交叉类型合并了可选性)
const props: Merged = {
children: "text" // ✅ 合法(string 是合法 ReactNode)
};
const props2: Merged = {
children: <div/> // ❌ 错误:JSX 元素不是 string
};
const props3: Merged = {}; // ❌ 错误:原类型要求 children 必填
1.2 React.FC 的泛型集成原理
React 源码中 React.FC 的定义如下:
typescript
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
// 其他静态属性...
}
用户传入 Props 类型 P,React.FC 将 P 传递给 PropsWithChildren
,最终得到的 props 类型为 P & { children?: ReactNode }
示例代码如下:
const MyComponent: React.FC<{ title: string }> = (props) => {
// props 自动获得 children 属性
return <div>{props.title}{props.children}</div>;
};
相当于
// 步骤 1:用户传入 P = { title: string }
type Props = { title: string };
// 步骤 2:应用 PropsWithChildren
type FinalProps = PropsWithChildren<Props> = {
title: string;
children?: React.ReactNode;
};
// 步骤 3:组件函数签名变为
(props: FinalProps) => ReactElement | null;
最后整体出来的效果,我们看React.FC和普通函数组件的对比就能很明显地看出区别了。
// 使用 React.FC
const ComponentA: React.FC<{ title: string }> = ({ title, children }) => (
<div>{title} {children}</div>
);
// 普通函数组件
const ComponentB = ({ title, children }: {
title: string;
children?: React.ReactNode // 需要显式声明
}) => <div>{title} {children}</div>;
2. React.FC<> 对于返回类型是显式的,而普通函数版本是隐式的(否则需要附加注释)。
// React.FC 强制返回类型为 ReactElement | null
const ValidComponent: React.FC = () => <div>Hello</div>; // ✅
const InvalidComponent: React.FC = () => "text"; // ❌ 返回字符串不符合要求
ReactElement | null 是 React 函数组件(Function Component)的 合法返回类型,它定义了组件可以返回的有效内容类型。这是 React 类型系统的核心约束之一,用于确保组件渲染行为的类型安全。
为什么,因为ReactElement本质上是JSX语法或 React.createElement() 创建的 虚拟 DOM 对象,null表示组件不渲染任何内容,而React函数组件的核心职责就是返回描述UI的React元素。
// JSX 编译后:
const element = <div>Hello</div>;
// 等价于:
const element = React.createElement('div', null, 'Hello');
以下对比就可以清晰地看出对于函数组件来说上述优化的优势
// 无类型约束
const Component = () => {
return "text"; // ✅ 不会报错(但运行时可能出错)
};
const Component: React.FC = () => {
return "text"; // ❌ 编译时立即报错
};
3. 使用React.FC写 React 组件的时候,不能用setState,取而代之的是useState()、useEffect等 Hook API。
在 React 中,setState 是类组件特有的状态更新方法,而函数组件使用 useState Hook 返回的 setter 函数。
setState
import React, { Component } from 'react';
class ClassCounter extends Component {
state = { count: 0 }; // 初始化状态
// 使用 setState 更新状态
increment = () => {
this.setState({ count: this.state.count + 1 }); // ✅ 对象式更新
};
decrement = () => {
this.setState(prevState => ({ // ✅ 函数式更新(依赖前值)
count: prevState.count - 1
}));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
);
}
}
useState
import React, { useState } from 'react';
const FunctionCounter: React.FC = () => {
const [count, setCount] = useState(0); // 初始化状态
// 使用 setCount 更新状态
const increment = () => {
setCount(count + 1); // ✅ 直接更新
};
const decrement = () => {
setCount(prevCount => prevCount - 1); // ✅ 函数式更新
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
};
React 16.8 前,函数组件是一个纯函数,纯函数仅接收 props并返回JSX,无生命周期/状态管理,是无状态组件。
React 16.8+版本React.FC成为了一个函数式组件,引入了Hooks之后,可通过 useState 管理状态并通过 useEffect 处理副作用,是有状态组件,也不再是严格纯函数了。
// 使用 React.FC 声明 + Hooks 管理状态
const Counter: React.FC = () => {
const [count, setCount] = useState(0); // ✅ 完全合法
return <button onClick={() => setCount(c => c+1)}>{count}</button>;
}
4. React.FC提供了类型检查和自动完成的静态属性:displayName,propTypes和defaultProps(注意:defaultProps与React.FC结合使用会存在一些问题)。
4.1 displayName(调试标识)
// 定义一个匿名箭头函数
const MyComponent: React.FC = () => <div>Hello</div>;
// 类型自动包含 displayName
MyComponent.displayName = "MyAwesomeComponent"; // ✅ 类型检查通过
// React DevTools 显示组件名称:
// <MyAwesomeComponent> 而不是 <Anonymous>
// <Anonymous> 是 React DevTools 用来表示无法确定名称的组件的占位符名称
4.2 propTypes(运行时类型检查)
相当于提供了双重保障,TypeScript编译时也会报错,propTypes运行时也会进行类型检查。
import PropTypes from 'prop-types';
const Greeting: React.FC<{ name: string }> = ({ name }) => (
<h1>Hello, {name}</h1>
);
// 类型自动包含 propTypes
Greeting.propTypes = {
name: PropTypes.string.isRequired // ✅ 类型匹配
};
// 错误示例(TS 会报错):
Greeting.propTypes = {
name: PropTypes.number // ❌ 类型不匹配
};
最佳实践是,在 TypeScript 项目中,优先依赖静态类型,仅在需要与 JavaScript 生态交互时补充 PropTypes。两者结合为组件提供从开发到运行的全周期类型安全。
4.3 defaultProps(默认属性值)
以下是ES6默认参数和defaultProps的区别对比
interface Props {
size?: 'sm' | 'md' | 'lg';
}
// ES6默认参数
const Button: React.FC<Props> = ({ size = 'md' }) => (
<button className={`btn-${size}`}>Click</button>
);
// 类型自动包含 defaultProps
Button.defaultProps = {
size: 'md' // ✅ 类型匹配
};
问题在于:TypeScript 不会自动将 defaultProps 的默认值注入到 props 类型中。这导致以下矛盾行为:
// 正确方式:ES6 默认参数
const Button: React.FC<Props> = ({ size = 'md' }) => { ... }
// ✅ 使用时:<Button /> → size 自动为 'md'
// 问题方式:使用 defaultProps
Button.defaultProps = { size: 'md' };
// ❌ 使用时:<Button /> → TS 认为 size 是 undefined | 'sm' | 'md' | 'lg'
本来作为一个可选属性 size可以传入 sm、md、lg和undefined,但是有了默认值md,理论上size就不可能是undefined了,es6默认参数可以在这时进行自动的收窄,但是defaultProps不行
// 原始类型:
type Props = { size?: 'sm' | 'md' | 'lg' };
// 解构 + 默认值 等价于:
const size: Props['size'] = props.size ?? 'md';
// => 类型变为 NonNullable<Props['size']> | 'md'
interface Props {
size?: 'sm' | 'md' | 'lg'; // 可选属性
}
const Button: React.FC<Props> = (props) => {
// 即使设置了 defaultProps,TS 仍认为 props.size 可能为 undefined
console.log(props.size); // type: 'sm' | 'md' | 'lg' | undefined ❌
return <button>{props.size}</button>; // 可能渲染 "undefined"!
};


2627

被折叠的 条评论
为什么被折叠?



