useEffect使用方式及注意事项

React useEffect 使用方式及注意事项

useEffect 是 React Hooks 中处理副作用的核心 API,用于替代类组件中的生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount)。以下是其使用方式及关键注意事项:


一、基本使用方式

1. 语法

useEffect(() => {
  // 副作用逻辑
  return () => { /* 清理函数(可选)*/ };
}, [dependencies]);

2. 不同场景的使用

  • 每次都执行
    不传第二个参数,每次都执行:

    useEffect(() => {
      console.log('组件渲染时执行');
    });
    
  • 执行一次(挂载时)
    依赖数组为空,仅在组件挂载时执行:

    useEffect(() => {
      console.log('组件挂载时执行');
      fetchData(); // 数据请求
    }, []);
    
  • 依赖变化时执行
    当依赖项变化时,触发副作用:

    const [count, setCount] = useState(0);
    useEffect(() => {
      console.log('count变化时执行:', count);
    }, [count]); // 依赖 count
    
  • 清理副作用
    返回一个函数用于清理(如取消订阅、清除定时器):

    useEffect(() => {
      const timer = setInterval(() => {
        console.log('定时器运行');
      }, 1000);
      return () => clearInterval(timer); // 清理定时器
    }, []);
    

二、注意事项

1. 正确处理依赖数组

  • 依赖缺失:可能导致闭包问题(使用过期的变量)。

    // ❌ 错误:依赖缺失
    const [count, setCount] = useState(0);
    useEffect(() => {
      const timer = setInterval(() => {
        console.log(count); // 始终输出初始值 0
      }, 1000);
      return () => clearInterval(timer);
    }, []); // 缺少 count 依赖
    
    // ✅ 正确:添加依赖或使用函数式更新
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(prev => prev + 1); // 通过函数式更新获取最新值
      }, 1000);
      return () => clearInterval(timer);
    }, []);
    
  • 依赖冗余:不必要的依赖可能导致频繁触发。
    建议:使用 ESLint 规则 react-hooks/exhaustive-deps 自动检测依赖。

2. 避免无限循环

当副作用内更新依赖的状态时,可能触发无限循环:

// ❌ 错误:每次更新 count 都会触发 effect
const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1); // 触发重新渲染,再次执行 effect
}, [count]); 

// ✅ 解决方案:确保更新是必要的,或使用空依赖
// 例如,仅在挂载时初始化
useEffect(() => {
  setCount(1);
}, []); 

3. 异步操作处理

useEffect 的回调函数不能直接使用 async,但可以内部定义异步逻辑:

// ✅ 正确:在 effect 内部定义异步函数
useEffect(() => {
  const fetchData = async () => {
    const res = await api.getData();
    setData(res);
  };
  fetchData();
}, []);

// ✅ 或使用 IIFE(立即执行函数)
useEffect(() => {
  (async () => {
    const res = await api.getData();
    setData(res);
  })();
}, []);

4. 清理副作用

如果副作用需要清理(如订阅、定时器),必须返回清理函数:

useEffect(() => {
  const subscription = eventEmitter.subscribe(() => {});
  return () => subscription.unsubscribe(); // 清理订阅
}, []);

5. 执行时机

  • useEffect 在浏览器完成布局与绘制异步执行。
  • 若需同步执行(如测量布局),使用 useLayoutEffect

6. 开发环境下的严格模式

React 18+ 在开发模式下会重复挂载组件以检测副作用问题:

// 示例:effect 执行两次
useEffect(() => {
  console.log('Effect执行'); // 开发模式下输出两次
}, []);

解决方案:确保副作用和清理函数是幂等的(多次执行无副作用)。

7. 避免条件语句中使用 Hook

Hook 必须在组件顶层调用,不可嵌套在条件或循环中:

// ❌ 错误:条件语句中使用 useEffect
if (isLoaded) {
  useEffect(() => { /* ... */ }, []); 
}

// ✅ 正确:将条件移至 effect 内部
useEffect(() => {
  if (isLoaded) {
    // 执行逻辑
  }
}, [isLoaded]); 

三、最佳实践

  1. 拆分副作用:将不相关的副作用拆分到多个 useEffect
  2. 优先函数式更新:当更新依赖前一个状态时,使用函数式更新避免依赖:
    const [count, setCount] = useState(0);
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(prev => prev + 1); // 不依赖 count
      }, 1000);
      return () => clearInterval(timer);
    }, []); 
    
  3. 性能优化:对于复杂计算,使用 useMemouseCallback 减少不必要的 effect 触发。

总结

useEffect 是管理副作用的强大工具,但需谨慎处理依赖项、避免无限循环,并合理清理资源。结合 ESLint 规则和 React 严格模式,可显著提高代码健壮性。

posted @ 2025-03-29 23:14  奔付山河  阅读(533)  评论(0)    收藏  举报