
文章目录
在 React 的发展历程中,组件的编写方式经历了巨大的演变。从最初的 类组件(Class Components) 一统天下,到后来 函数组件(Function Components) 凭借 Hooks 实现逆袭,成为当前官方推荐的主流。理解这两种组件的区别、优缺点以及适用场景,是每一位 React 开发者必备的核心知识。本文将深入剖析两者,带你彻底掌握它们。
一、 什么是类组件与函数组件?
在深入区别之前,我们先通过一个流程图快速建立宏观认知,了解如何根据场景在两者之间做出选择:
1. 类组件 (Class Components)
类组件是 ES6 的 class,它通过继承 React.Component 来创建,并使用 render 方法返回需要渲染的 JSX。
核心特征:
- 必须继承
React.Component或React.PureComponent。 - 必须定义
render()方法,该方法返回 JSX 元素。 - 拥有内部状态 (
this.state) 和生命周期方法 (如componentDidMount)。
代码示例:
import React, { Component } from 'react';
// 定义一个类组件
class ClassComponentExample extends Component {
// 1. 构造函数,初始化状态 (state)
constructor(props) {
super(props); // 必须调用 super(props)
this.state = {
count: 0,
message: 'Hello, Class Component!'
};
// 2. 为了解决 this 指向问题,需要手动绑定方法
this.handleClick = this.handleClick.bind(this);
}
// 3. 自定义方法:更新状态
handleClick() {
this.setState({
count: this.state.count + 1
});
}
// 4. 生命周期方法:组件挂载后执行
componentDidMount() {
console.log('Component did mount!');
}
// 5. 生命周期方法:组件更新后执行
componentDidUpdate() {
console.log('Component did update! Count is:', this.state.count);
}
// 6. 必须定义的 render 方法
render() {
return (
<div>
<h1>{this.state.message}</h1>
<p>You clicked {this.state.count} times</p>
{/* 调用方法,注意 this 的绑定 */}
<button onClick={this.handleClick}>Click me</button>
{/* 也可以使用箭头函数避免绑定,但每次都会创建新函数 */}
{/* <button onClick={() => this.handleClick()}>Click me</button> */}
</div>
);
}
}
export default ClassComponentExample;
2. 函数组件 (Function Components)
函数组件本质上就是一个 JavaScript 函数,它接收 props 作为参数,并直接返回需要渲染的 JSX。
核心特征 (Hooks 之前):
- 只是一个普通的 JavaScript 函数。
- 没有内部状态 (
state)。 - 没有生命周期方法。
- 被称为“无状态组件”或“展示组件”,只负责接收 props 和渲染 UI。
核心特征 (Hooks 之后):
- 通过 Hooks (如
useState,useEffect) 拥有了管理状态和副作用的能力。 - 功能上已经完全等同于类组件,且代码更简洁。
代码示例 (使用 Hooks):
import React, { useState, useEffect } from 'react';
// 定义一个函数组件(使用 Hooks)
function FunctionComponentExample(props) {
// 1. 使用 useState Hook 定义状态
const [count, setCount] = useState(0);
const [message, setMessage] = useState('Hello, Function Component!');
// 2. 自定义函数:更新状态(无需担心 this 绑定)
const handleClick = () => {
setCount(prevCount => prevCount + 1); // 使用函数式更新
};
// 3. 使用 useEffect Hook 模拟生命周期
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
console.log('Component did mount or update! Count is:', count);
}, [count]); // 依赖数组:只有当 count 变化时,effect 才会重新执行
// 4. 模拟 componentDidMount(依赖数组为空)
useEffect(() => {
console.log('This only runs once, like componentDidMount');
}, []);
// 5. 直接返回 JSX,无需 render 方法
return (
<div>
<h1>{message}</h1>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default FunctionComponentExample;
二、 核心区别对比
| 特性 | 类组件 (Class Components) | 函数组件 (Function Components with Hooks) |
|---|---|---|
| 定义方式 | ES6 Class,继承 React.Component | JavaScript 函数(普通或箭头) |
| 状态管理 | this.state 和 this.setState() | useState, useReducer Hook |
| 生命周期 | 生命周期方法 (componentDidMount, componentDidUpdate 等) | useEffect Hook 模拟所有生命周期 |
this 指向 | 需要手动绑定或使用类字段语法,容易出错 | 没有 this,彻底避免指向问题 |
| 代码复杂度 | 代码相对冗长,尤其是绑定和生命周期方法 | 代码更简洁,逻辑更聚合(相关代码在一起) |
| 逻辑复用 | 高阶组件 (HOC)、Render Props | 自定义 Hooks,逻辑复用更简单自然 |
| 学习曲线 | 需要理解 ES6 Class、this、生命周期概念 | 需要理解 Hooks 的规则(如调用顺序)和闭包 |
| 性能 | 使用 PureComponent 或 shouldComponentUpdate 优化 | 使用 React.memo, useMemo, useCallback 优化 |
| 未来趋势 | 遗留代码中常见,React 团队不推荐在新项目中使用 | 当前和未来的主流,React 官方推荐 |
三、 深入理解关键差异
1. 状态管理:心智模型的转变
- 类组件: 状态是一个单一的对象 (
this.state),更新时通过this.setState()合并更新。this.setState({ count: this.state.count + 1 }); // 合并更新 - 函数组件: 状态被分解为多个独立的 state 变量,通过各自的 setter 函数更新。
const [count, setCount] = useState(0); const [name, setName] = useState('John'); setCount(c => c + 1); // 函数式更新,避免闭包陷阱
2. 生命周期 vs 副作用 Effect
这是两者最大的概念差异。
-
类组件: 基于生命周期方法。将不同的逻辑分散到不同的方法中。
componentDidMount:发起网络请求、订阅事件。componentDidUpdate:根据 props 或 state 变化执行操作。componentWillUnmount:清除订阅、取消请求。- 问题:不相关的代码放在一起(如设置事件监听和请求数据),而相关的代码却被拆分(如订阅和取消订阅)。
-
函数组件: 基于副作用 Effect。思维模式是“同步到某个状态”。
useEffect允许你根据 依赖项 来执行和清理副作用。- 它将原本分散在不同生命周期中的相关代码聚合在了一起。
useEffect(() => { // 1. 建立订阅(相当于 didMount 和 didUpdate 中与 count 相关的部分) const subscription = props.source.subscribe(); // 2. 返回一个清理函数(相当于 willUnmount 和 didUpdate 中下次 effect 执行前) return () => { subscription.unsubscribe(); }; }, [props.source]); // 3. 依赖项:只有当 props.source 改变时才会重新执行这种“关注点分离”的方式使得代码更容易理解和维护。
3. 逻辑复用:从模式到 Hook
-
类组件: 使用高阶组件 (HOC) 或 Render Props。这些模式虽然强大,但会导致“嵌套地狱”(Wrapper Hell),降低组件可读性。
// HOC 示例 const EnhancedComponent = withHOC(MyComponent); // Render Props 示例 <DataProvider render={data => <Child data={data} />} /> -
函数组件: 使用自定义 Hook。它允许你提取组件逻辑成为可重用的函数,且不会增加组件树的嵌套层级。
// 自定义 Hook: useFriendStatus function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { // 订阅好友状态逻辑... }, [friendID]); return isOnline; } // 在多个组件中使用它 function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); // 像调用函数一样使用逻辑 return <div>{isOnline ? 'Online' : 'Offline'}</div>; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); // 逻辑复用! return <li style={{ color: isOnline ? 'green' : 'black' }}>{props.friend.name}</li>; }
四、 总结与最佳实践
-
对于新项目和新组件:
毫不犹豫地选择函数组件 + Hooks。 这是 React 现在和未来的发展方向,代码更简洁,逻辑更清晰,复用性更强。 -
对于现有项目:
- 如果是旧的类组件,没有必要立刻重写为函数组件,除非你在进行重大重构或需要引入新的 Hooks 逻辑。
- 在新的开发中,逐步采用函数组件。
-
类组件的遗留用例:
getSnapshotBeforeUpdate和componentDidCatch生命周期方法目前还没有完全对等的 Hooks 实现。因此,错误边界(Error Boundaries)目前必须用类组件来实现。
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }
结论:
函数组件与 Hooks 的结合代表了 React 编程模式的一次重大飞跃。它解决了类组件在长期实践中暴露出的诸多问题,如复杂的 this 绑定、难以理解的生命周期和逻辑复用困难等。虽然学习 Hooks 需要一些新的心智模型,但其带来的好处是巨大的。拥抱函数组件,意味着你正在拥抱 React 的未来。

4457

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



