react中hooks之useEffect 用法总结

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

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

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

2. useEffect 的执行时机

2.1 省略依赖项

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

2.2 指定依赖项

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

2.3 空数组依赖项

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

3. 常见问题和最佳实践

3.1 避免依赖项循环

jsx 复制代码
// ❌ 错误示例:造成无限循环
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 分离关注点

jsx 复制代码
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);

  // ✅ 分开声明不同功能的 useEffect
  useEffect(() => {
    // 获取用户信息
    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 事件监听示例

jsx 复制代码
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 定时器示例

jsx 复制代码
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 数据订阅示例

jsx 复制代码
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]); // 依赖于 dataSource

  return <div>{data ? <DataView data={data} /> : 'Loading...'}</div>;
}

4.5 WebSocket 连接示例

jsx 复制代码
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 表单自动保存

jsx 复制代码
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>
      <textarea
        value={content}
        onChange={e => setContent(e.target.value)}
      />
      {saving && <span>Saving...</span>}
    </div>
  );
}

5.2 实时搜索

jsx 复制代码
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>
      <input
        value={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,我们可以优雅地处理组件的副作用,实现更复杂的交互逻辑,同时保持代码的可维护性和性能。

相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax