react中hooks之useEffect 用法总结

打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

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

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


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

2.1 省略依赖项

  1. useEffect(() => {
  2.   console.log('每次渲染都会执行');
  3. }); // 没有依赖项数组
复制代码


  • 组件每次渲染都会实行
  • 包罗首次渲染和后续更新
2.2 指定依赖项

  1. useEffect(() => {
  2.   console.log(`count 发生变化:${count}`);
  3. }, [count]); // 依赖于 count
复制代码


  • 首次渲染时实行
  • 依赖项发生变化时实行
  • 多个依赖项时,任意一个变化都会触发实行
2.3 空数组依赖项

  1. useEffect(() => {
  2.   console.log('只在组件挂载时执行一次');
  3. }, []); // 空数组
复制代码


  • 仅在组件首次渲染(挂载)时实行一次
  • 雷同于 class 组件的 componentDidMount
3. 常见问题和最佳实践

3.1 避免依赖项循环

  1. // ❌ 错误示例:造成无限循环
  2. function BadExample() {
  3.   const [count, setCount] = useState(0);
  4.   
  5.   useEffect(() => {
  6.     setCount(count + 1); // 直接修改依赖项
  7.   }, [count]);
  8.   
  9.   return <div>{count}</div>;
  10. }
  11. // ✅ 正确示例:使用函数式更新
  12. function GoodExample() {
  13.   const [count, setCount] = useState(0);
  14.   
  15.   useEffect(() => {
  16.     setCount(prevCount => prevCount + 1);
  17.   }, []); // 不需要依赖项
  18.   
  19.   return <div>{count}</div>;
  20. }
复制代码
3.2 分离关注点

  1. function UserProfile({ userId }) {
  2.   const [user, setUser] = useState(null);
  3.   const [posts, setPosts] = useState([]);
  4.   // ✅ 分开声明不同功能的 useEffect
  5.   useEffect(() => {
  6.     // 获取用户信息
  7.     fetchUser(userId).then(setUser);
  8.   }, [userId]);
  9.   useEffect(() => {
  10.     // 获取用户帖子
  11.     fetchUserPosts(userId).then(setPosts);
  12.   }, [userId]);
  13.   return (
  14.     <div>
  15.       <UserInfo user={user} />
  16.       <UserPosts posts={posts} />
  17.     </div>
  18.   );
  19. }
复制代码
4. 清除副作用

4.1 清理函数的实行时机

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


  • 组件卸载时
  • 下一次 effect 实行前
4.2 变乱监听示例

  1. function WindowWidth() {
  2.   const [width, setWidth] = useState(window.innerWidth);
  3.   
  4.   useEffect(() => {
  5.     const handleResize = () => setWidth(window.innerWidth);
  6.    
  7.     // 添加事件监听
  8.     window.addEventListener('resize', handleResize);
  9.    
  10.     // 清理函数
  11.     return () => {
  12.       window.removeEventListener('resize', handleResize);
  13.     };
  14.   }, []); // 空依赖数组,只在挂载和卸载时执行
  15.   return <div>Window width: {width}</div>;
  16. }
复制代码
4.3 定时器示例

  1. function Timer() {
  2.   const [count, setCount] = useState(0);
  3.   useEffect(() => {
  4.     const timer = setInterval(() => {
  5.       setCount(c => c + 1);
  6.     }, 1000);
  7.     // 清理函数:组件卸载时清除定时器
  8.     return () => clearInterval(timer);
  9.   }, []); // 空依赖数组
  10.   return <div>Count: {count}</div>;
  11. }
复制代码
4.4 数据订阅示例

  1. function DataSubscriber({ dataSource }) {
  2.   const [data, setData] = useState(null);
  3.   useEffect(() => {
  4.     let isSubscribed = true;
  5.     const handleData = (newData) => {
  6.       if (isSubscribed) {
  7.         setData(newData);
  8.       }
  9.     };
  10.     // 订阅数据源
  11.     const subscription = dataSource.subscribe(handleData);
  12.     // 清理函数:取消订阅
  13.     return () => {
  14.       isSubscribed = false;
  15.       subscription.unsubscribe();
  16.     };
  17.   }, [dataSource]); // 依赖于 dataSource
  18.   return <div>{data ? <DataView data={data} /> : 'Loading...'}</div>;
  19. }
复制代码
4.5 WebSocket 连接示例

  1. function WebSocketComponent({ url }) {
  2.   const [messages, setMessages] = useState([]);
  3.   useEffect(() => {
  4.     const ws = new WebSocket(url);
  5.     ws.onmessage = (event) => {
  6.       setMessages(prev => [...prev, event.data]);
  7.     };
  8.     // 清理函数:关闭 WebSocket 连接
  9.     return () => {
  10.       ws.close();
  11.     };
  12.   }, [url]);
  13.   return (
  14.     <div>
  15.       {messages.map((msg, index) => (
  16.         <div key={index}>{msg}</div>
  17.       ))}
  18.     </div>
  19.   );
  20. }
复制代码
5. 现实应用场景

5.1 表单主动生存

  1. function AutoSaveForm() {
  2.   const [content, setContent] = useState('');
  3.   const [saving, setSaving] = useState(false);
  4.   useEffect(() => {
  5.     // 防抖处理
  6.     const timeoutId = setTimeout(() => {
  7.       if (content) {
  8.         setSaving(true);
  9.         saveContent(content)
  10.           .then(() => setSaving(false));
  11.       }
  12.     }, 1000);
  13.     return () => clearTimeout(timeoutId);
  14.   }, [content]);
  15.   return (
  16.     <div>
  17.       <textarea
  18.         value={content}
  19.         onChange={e => setContent(e.target.value)}
  20.       />
  21.       {saving && <span>Saving...</span>}
  22.     </div>
  23.   );
  24. }
复制代码
5.2 及时搜索

  1. function SearchComponent() {
  2.   const [query, setQuery] = useState('');
  3.   const [results, setResults] = useState([]);
  4.   useEffect(() => {
  5.     // 避免空查询
  6.     if (!query.trim()) {
  7.       setResults([]);
  8.       return;
  9.     }
  10.     const abortController = new AbortController();
  11.     async function fetchResults() {
  12.       try {
  13.         const response = await fetch(
  14.           `/api/search?q=${query}`,
  15.           { signal: abortController.signal }
  16.         );
  17.         const data = await response.json();
  18.         setResults(data);
  19.       } catch (error) {
  20.         if (error.name === 'AbortError') {
  21.           // 忽略中止的请求错误
  22.           return;
  23.         }
  24.         console.error('搜索出错:', error);
  25.       }
  26.     }
  27.     const timeoutId = setTimeout(fetchResults, 300);
  28.     // 清理函数:取消请求和清除定时器
  29.     return () => {
  30.       clearTimeout(timeoutId);
  31.       abortController.abort();
  32.     };
  33.   }, [query]);
  34.   return (
  35.     <div>
  36.       <input
  37.         value={query}
  38.         onChange={e => setQuery(e.target.value)}
  39.         placeholder="搜索..."
  40.       />
  41.       <ul>
  42.         {results.map(result => (
  43.           <li key={result.id}>{result.title}</li>
  44.         ))}
  45.       </ul>
  46.     </div>
  47.   );
  48. }
复制代码
6. 最佳实践总结


  • 保持 effect 函数简便,专注于单一功能
  • 公道利用依赖项,避免不须要的实行
  • 始终清理副作用,防止内存泄漏
  • 利用条件语句控制 effect 的实行
  • 考虑利用自定义 Hook 封装常用的副作用逻辑
  • 在开辟环境下利用 ESLint 的 exhaustive-deps 规则检查依赖项
  • 利用 useCallback 和 useMemo 优化依赖项
通过公道利用 useEffect,我们可以优雅地处理组件的副作用,实现更复杂的交互逻辑,同时保持代码的可维护性和性能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连密封材料

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表