react中hooks之useEffect 用法总结

embedded/2025/1/16 10:13:59/

1. 什么是函数的副作用(Side Effects)

副作用是指在组件渲染过程中,除了返回 JSX 之外的其他操作,例如:

  • 数据获取(API 调用)
  • 订阅数据源
  • 手动修改 DOM
  • 设置定时器
  • 存储数据
  • 日志记录
    纯函数是特定的输入只会有特定的输出,也就是说组件会输出特定的DOM给浏览器渲染,除去这份逻辑以外的操作就称之为副作用,比如获取数据,监听,订阅等等

2. useEffect 的执行时机

2.1 省略依赖项

useEffect(() => {console.log('每次渲染都会执行');
}); // 没有依赖项数组
  • 组件每次渲染都会执行
  • 包括首次渲染和后续更新

2.2 指定依赖项

useEffect(() => {console.log(`count 发生变化:${count}`);
}, [count]); // 依赖于 count
  • 首次渲染时执行
  • 依赖项发生变化时执行
  • 多个依赖项时,任意一个变化都会触发执行

2.3 空数组依赖项

useEffect(() => {console.log('只在组件挂载时执行一次');
}, []); // 空数组
  • 仅在组件首次渲染(挂载)时执行一次
  • 类似于 class 组件的 componentDidMount

3. 常见问题和最佳实践

3.1 避免依赖项循环

// ❌ 错误示例:造成无限循环
function BadExample() {const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1); // 直接修改依赖项}, [count]);return <div>{count}</div>;
}// ✅ 正确示例:使用函数式更新
function GoodExample() {const [count, setCount] = useState(0);useEffect(() => {setCount(prevCount => prevCount + 1);}, []); // 不需要依赖项return <div>{count}</div>;
}

3.2 分离关注点

function UserProfile({ userId }) {const [user, setUser] = useState(null);const [posts, setPosts] = useState([]);// ✅ 分开声明不同功能的 useEffectuseEffect(() => {// 获取用户信息fetchUser(userId).then(setUser);}, [userId]);useEffect(() => {// 获取用户帖子fetchUserPosts(userId).then(setPosts);}, [userId]);return (<div><UserInfo user={user} /><UserPosts posts={posts} /></div>);
}

4. 清除副作用

4.1 清理函数的执行时机

清理函数会在以下情况执行:

  • 组件卸载时
  • 下一次 effect 执行前

4.2 事件监听示例

function WindowWidth() {const [width, setWidth] = useState(window.innerWidth);useEffect(() => {const handleResize = () => setWidth(window.innerWidth);// 添加事件监听window.addEventListener('resize', handleResize);// 清理函数return () => {window.removeEventListener('resize', handleResize);};}, []); // 空依赖数组,只在挂载和卸载时执行return <div>Window width: {width}</div>;
}

4.3 定时器示例

function Timer() {const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {setCount(c => c + 1);}, 1000);// 清理函数:组件卸载时清除定时器return () => clearInterval(timer);}, []); // 空依赖数组return <div>Count: {count}</div>;
}

4.4 数据订阅示例

function DataSubscriber({ dataSource }) {const [data, setData] = useState(null);useEffect(() => {let isSubscribed = true;const handleData = (newData) => {if (isSubscribed) {setData(newData);}};// 订阅数据源const subscription = dataSource.subscribe(handleData);// 清理函数:取消订阅return () => {isSubscribed = false;subscription.unsubscribe();};}, [dataSource]); // 依赖于 dataSourcereturn <div>{data ? <DataView data={data} /> : 'Loading...'}</div>;
}

4.5 WebSocket 连接示例

function WebSocketComponent({ url }) {const [messages, setMessages] = useState([]);useEffect(() => {const ws = new WebSocket(url);ws.onmessage = (event) => {setMessages(prev => [...prev, event.data]);};// 清理函数:关闭 WebSocket 连接return () => {ws.close();};}, [url]);return (<div>{messages.map((msg, index) => (<div key={index}>{msg}</div>))}</div>);
}

5. 实际应用场景

5.1 表单自动保存

function AutoSaveForm() {const [content, setContent] = useState('');const [saving, setSaving] = useState(false);useEffect(() => {// 防抖处理const timeoutId = setTimeout(() => {if (content) {setSaving(true);saveContent(content).then(() => setSaving(false));}}, 1000);return () => clearTimeout(timeoutId);}, [content]);return (<div><textareavalue={content}onChange={e => setContent(e.target.value)}/>{saving && <span>Saving...</span>}</div>);
}

5.2 实时搜索

function SearchComponent() {const [query, setQuery] = useState('');const [results, setResults] = useState([]);useEffect(() => {// 避免空查询if (!query.trim()) {setResults([]);return;}const abortController = new AbortController();async function fetchResults() {try {const response = await fetch(`/api/search?q=${query}`,{ signal: abortController.signal });const data = await response.json();setResults(data);} catch (error) {if (error.name === 'AbortError') {// 忽略中止的请求错误return;}console.error('搜索出错:', error);}}const timeoutId = setTimeout(fetchResults, 300);// 清理函数:取消请求和清除定时器return () => {clearTimeout(timeoutId);abortController.abort();};}, [query]);return (<div><inputvalue={query}onChange={e => setQuery(e.target.value)}placeholder="搜索..."/><ul>{results.map(result => (<li key={result.id}>{result.title}</li>))}</ul></div>);
}

6. 最佳实践总结

  1. 保持 effect 函数简洁,专注于单一功能
  2. 合理使用依赖项,避免不必要的执行
  3. 始终清理副作用,防止内存泄漏
  4. 使用条件语句控制 effect 的执行
  5. 考虑使用自定义 Hook 封装常用的副作用逻辑
  6. 在开发环境下使用 ESLint 的 exhaustive-deps 规则检查依赖项
  7. 使用 useCallback 和 useMemo 优化依赖项

通过合理使用 useEffect,我们可以优雅地处理组件的副作用,实现更复杂的交互逻辑,同时保持代码的可维护性和性能。


http://www.ppmy.cn/embedded/154367.html

相关文章

深入理解循环神经网络(RNN):原理、应用与挑战

引言 在深度学习的众多模型中&#xff0c;循环神经网络&#xff08;RNN&#xff09;因其对序列数据处理的特性而备受关注。无论是自然语言处理、时间序列预测&#xff0c;还是语音识别&#xff0c;RNN都展现出了强大的能力。然而&#xff0c;RNN的内部机制及其在实际应用中的优…

康谋产品 | 深度自然匿名化:隐私保护与视觉完整性并存的未来!

在科技迅速发展的时代&#xff0c;保护个人隐私的需求日益增长&#xff0c;有效匿名化技术的重要性不容忽视。无论是针对敏感的图像、视频&#xff0c;还是数据&#xff0c;在隐私保护与保持视觉完整性之间取得平衡至关重要。虽然模糊化一直是匿名化的常用选择&#xff0c;但一…

Kafka 超级简述

Kafka 就是一个 分布式的消息系统&#xff0c;它帮助不同的系统和应用之间传递信息。可以把它想象成一个超级高效的 “邮局”&#xff1a; 生产者&#xff08;Producer&#xff09; 就是把信息&#xff08;消息&#xff09;送到这个 “邮局” 的人。消费者&#xff08;Consume…

web前端第六次作业---制作网页页面

制作网页页面 代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><s…

Java 面试题 - ArrayList 和 LinkedList 的区别,哪个集合是线程安全的?

Java 面试题 - ArrayList 和 LinkedList 的区别&#xff0c;哪个集合是线程安全的&#xff1f; 在 Java 开发中&#xff0c;ArrayList和LinkedList是两个常用的集合类&#xff0c;它们在数据结构和性能上有诸多不同&#xff0c;同时线程安全性也各有特点。深入理解这些差异&am…

《解决OpenMP运行时库副本问题:解锁高效编程》

一、OpenMP 运行时库副本问题的引入 在当今的计算机科学领域&#xff0c;并行计算已经成为提升计算效率、加速程序运行的关键手段。OpenMP&#xff08;Open Multi - Processing&#xff09;作为一种广泛应用于共享内存并行系统的多线程编程模型&#xff0c;凭借其易于使用和集成…

C# Winform:项目引入SunnyUI后,显示模糊

在使用WinForms并引入SunnyUI等第三方UI库后&#xff0c;如果运行出来的窗口出现模糊问题&#xff0c;大概率是由于DPI设置有问题&#xff0c;解决方法如下&#xff1a; 在Visual Studio中&#xff0c;右击项目名称&#xff0c;选择“添加”->“新项”。 在“添加新项”对话…

Docker启动达梦 rman恢复

目录标题 1. 主库备份2. Docker启动备库3. 备库修改属组4. 开始恢复5. 连接数据库配置归档 & Open6. 检查数据 关于达梦数据库&#xff08;DMDBMS&#xff09;的主库备份、Docker启动备库、恢复备份以及配置归档和打开数据库的详细步骤。 1. 主库备份 # 使用达梦数据库备…