组件
函数组件与类组件
对比维度 |
函数组件 |
类组件 |
写法 |
简洁 |
冗长(需要构造函数、this 绑定等) |
状态管理 |
useState |
this.state 和 this.setState |
生命周期 |
useEffect 等 Hook |
组件生命周期函数如 componentDidMount |
this 使用 |
不使用 |
必须绑定 |
代码复用 |
自定义 Hook 简单灵活 |
高阶组件和 render props 较繁琐 |
推荐程度 |
✅ 推荐用于新项目 |
⚠️ 支持,但不推荐新开发使用 |
函数组件
在 React 函数组件中,并没有类组件那样的生命周期方法(如 componentDidMount
、componentDidUpdate
、componentWillUnmount
),但我们可以使用 Hooks(主要是 useEffect
) 来模拟这些生命周期行为。
🧠 类组件生命周期 与 函数组件 Hook 的对照表
类组件生命周期 |
函数组件中的写法(Hook) |
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate |
useEffect(() => { ... }, [依赖项]) |
componentWillUnmount |
useEffect(() => { return () => { ... }; }, []) |
所有更新后触发(类似 render 后) |
useEffect(() => { ... }) (无第二参数) |
🎯 总结
目的 |
useEffect 写法 |
初始化时执行一次 |
useEffect(() => { ... }, []) |
某值变化时执行 |
useEffect(() => { ... }, [依赖项]) |
卸载前执行清理函数 |
useEffect(() => { return () => { ... } }, []) |
每次渲染都执行 |
useEffect(() => { ... }) |
✅ 示例:如何用 useEffect
模拟生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import React, { useEffect, useState } from 'react';
function LifeCycleDemo() { const [count, setCount] = useState(0);
useEffect(() => { console.log('组件挂载完成'); }, []);
useEffect(() => { console.log('count 更新为:', count); }, [count]);
useEffect(() => { return () => { console.log('组件即将卸载'); }; }, []);
return ( <div> <p>计数:{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }
|
📌 多个 useEffect 可以组合使用
React 支持一个组件中使用多个 useEffect
来分别处理不同的逻辑,这比类组件中将所有逻辑写在一个生命周期函数里更清晰:
1 2 3 4 5 6 7
| useEffect(() => { }, []);
useEffect(() => { }, []);
|
分别实现一个简单的计数器
✅ 一、函数组件版(使用 Hook)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { useState } from 'react';
function CounterFunction() { const [count, setCount] = useState(0);
return ( <div> <h2>函数组件计数器</h2> <p>当前计数:{count}</p> <button onClick={() => setCount(count + 1)}>增加</button> <button onClick={() => setCount(count - 1)}>减少</button> <button onClick={() => setCount(0)}>重置</button> </div> ); }
export default CounterFunction;
|
✅ 二、类组件版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import React, { Component } from 'react';
class CounterClass extends Component { constructor(props) { super(props); this.state = { count: 0, }; }
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
handleDecrement = () => { this.setState({ count: this.state.count - 1 }); };
handleReset = () => { this.setState({ count: 0 }); };
render() { return ( <div> <h2>类组件计数器</h2> <p>当前计数:{this.state.count}</p> <button onClick={this.handleIncrement}>增加</button> <button onClick={this.handleDecrement}>减少</button> <button onClick={this.handleReset}>重置</button> </div> ); } }
export default CounterClass;
|
如何实现组件的清理
组件清理是指在组件卸载或副作用更新之前,取消定时器、事件监听、网络请求、订阅等操作,避免内存泄漏和不必要的副作用。
✅ 一、函数组件中的清理(使用 useEffect
)
通过在 useEffect
中返回一个清理函数来完成:
📌 示例:清理定时器
1 2 3 4 5 6 7
| useEffect(() => { const timer = setInterval(() => { console.log('运行中...'); }, 1000);
return () => clearInterval(timer); }, []);
|
📌 示例:清理事件监听器
1 2 3 4 5 6
| useEffect(() => { const handleResize = () => console.log(window.innerWidth); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); }, []);
|
📌 示例:清理网络请求(推荐使用 AbortController)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| useEffect(() => { const controller = new AbortController();
fetch('https://api.example.com/data', { signal: controller.signal }) .then(res => res.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求已取消'); } });
return () => controller.abort(); }, []);
|
✅ 二、类组件中的清理(使用 componentWillUnmount
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MyComponent extends React.Component { componentDidMount() { this.timer = setInterval(() => { console.log('计时...'); }, 1000); }
componentWillUnmount() { clearInterval(this.timer); }
render() { return <div>类组件</div>; } }
|
✅ 三、常见需要清理的副作用类型
副作用类型 |
清理方法 |
定时器 |
clearInterval / clearTimeout |
DOM 事件监听器 |
removeEventListener |
WebSocket / Channel |
.close() |
自定义订阅(如 RxJS) |
.unsubscribe() |
异步请求 |
AbortController.abort() |
⚠️ 为什么必须清理副作用?
- 🧠 避免内存泄漏;
- ⛔ 避免卸载后仍尝试更新组件;
- 🔁 防止重复绑定或重复执行;
- 🛠 提高性能,避免潜在 bug。
Context API
Context API 是 React 提供的用来实现跨层级组件共享状态的解决方案,主要目的是避免在组件层层嵌套时,通过一层层 props 传递数据,非常适合全局或者半全局状态的管理。
Context API 的使用场景和详细解释
1. 传统的 props 传递痛点
假设你有一个组件树:
1 2 3 4 5 6 7
| <App> <Header> <UserMenu> <UserProfile /> </UserMenu> </Header> </App>
|
如果 UserProfile
需要显示当前登录用户的信息(如用户名、头像),传统做法是:
- 先在
App
组件拿到用户信息(比如从 API 或 Redux)
- 通过 props 一层层传递给
Header
,再传给 UserMenu
,再传给 UserProfile
问题:
- 当层级很深时,props 传递非常繁琐且臃肿(“props drilling”,props 钻取问题)
- 中间组件只是传递数据,并不真正用到这些数据,导致代码冗余且不易维护
2. Context API 解决方案
Context 允许你:
- 在某个顶层组件创建一个“上下文”(Context 对象),里面保存共享的数据
- 在任意深度的后代组件中通过
useContext
(函数组件)或者 Context.Consumer
(类组件)直接获取数据
- 这样就避免了层层传 props,简化了数据流转
3. 使用流程
① 创建 Context
1 2 3
| import React from 'react';
const UserContext = React.createContext(null);
|
② 在顶层提供数据(Provider)
1 2 3 4 5 6 7 8 9
| function App() { const user = { name: 'Alice', avatar: 'alice.png' };
return ( <UserContext.Provider value={user}> <Header /> </UserContext.Provider> ); }
|
③ 在任意深层子组件使用数据
1 2 3 4 5 6 7
| import React, { useContext } from 'react';
function UserProfile() { const user = useContext(UserContext);
return <div>欢迎,{user.name}</div>; }
|
- 类组件中使用
UserContext.Consumer
:
1 2 3
| <UserContext.Consumer> {user => <div>欢迎,{user.name}</div>} </UserContext.Consumer>
|
4. 适用场景举例
- 主题切换(深层组件需要访问主题色、字体大小等配置)
- 用户身份信息(登录用户信息,权限控制等)
- 多语言国际化(当前语言包)
- 全局配置项(API 地址、界面配置等)
5. 注意事项
- Context 适合共享“全局”或“跨多层”状态,不建议用它做频繁变更的本地状态(如表单输入)
- Context 里的数据变更,会导致所有消费组件重新渲染,注意性能优化
- 结合
React.memo
和 useMemo
可以降低无效渲染