解密useEffect依赖数组

大家好,我是小杨,一名有近6年前端开发经验的工程师。今天想和大家聊聊React中useEffect这个Hook的第二个参数。相信很多刚接触React的朋友都曾对这个参数感到困惑,我也是从那个阶段走过来的。还记得我第一次使用useEffect时,总是搞不清楚什么时候该传空数组,什么时候该传依赖项,结果导致组件出现各种奇怪的bug。

从一个实际案例说起

让我先分享一个我早期项目中的经历。当时我正在开发一个用户仪表板,需要根据用户ID的变化来获取用户数据。我是这样写的:

javascript 复制代码
function UserDashboard({ userId }) {
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUserData(data);
    };
    
    fetchData();
  }); // 注意:这里没有第二个参数
  
  return (
    <div>
      {/* 渲染用户数据 */}
    </div>
  );
}

你们看出问题了吗?没错,我忘记给useEffect添加第二个参数了!结果就是组件每次渲染都会发起API请求,造成了无限循环的请求,直接把我们的服务器给打挂了(笑)。

useEffect第二个参数的作用

那么,useEffect的第二个参数到底有什么作用呢?简单来说,它是一个依赖数组,用来告诉React只有当数组中的值发生变化时,才重新执行useEffect中的副作用函数。

1. 不传第二个参数

javascript 复制代码
useEffect(() => {
  // 每次组件渲染后都会执行
  console.log('Component rendered');
});

这种情况相当于componentDidMount + componentDidUpdate的组合,每次组件渲染后都会执行副作用函数。

2. 传空数组

javascript 复制代码
useEffect(() => {
  // 仅在组件挂载时执行一次
  console.log('Component mounted');
}, []);

这相当于componentDidMount,只在组件首次渲染后执行一次。

3. 传依赖项数组

javascript 复制代码
useEffect(() => {
  // 当userId或pageSize变化时执行
  fetchUserData(userId, pageSize);
}, [userId, pageSize]); // 依赖项数组

这是最有用的方式,只有当依赖项数组中的值发生变化时,才会重新执行副作用函数。

实际开发中的经验分享

经过这么多年的实践,我总结出了一些使用useEffect依赖数组的经验:

1. 不要忽略依赖项

ESLint的exhaustive-deps规则是你的好朋友,不要禁用它!我曾经为了快速解决问题而禁用这个规则,结果后来花了更多时间调试难以发现的bug。

2. 处理函数依赖

当副作用函数中使用到组件内定义的函数时,需要特别注意:

javascript 复制代码
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  const fetchUser = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    setUser(await response.json());
  }, [userId]);
  
  useEffect(() => {
    fetchUser();
  }, [fetchUser]); // 现在fetchUser是依赖项
  
  return /* 渲染逻辑 */;
}

这里我使用了useCallback来避免函数每次渲染都重新创建,从而避免useEffect不必要的执行。

3. 引用类型的问题

对于对象和数组这样的引用类型,要特别注意:

javascript 复制代码
useEffect(() => {
  // 一些操作
}, [user]); // 如果user是对象,即使内容相同但引用不同也会触发执行

这种情况下,可能需要使用深比较或者重新思考组件设计。

常见坑与解决方案

在我这些年的开发中,遇到了不少useEffect的坑,这里分享几个:

1. 无限循环

javascript 复制代码
// 错误的做法
const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1); // 会导致无限重新渲染
}, [count]);

解决方案是使用函数式更新:

javascript 复制代码
const [count, setCount] = useState(0);
useEffect(() => {
  setCount(prevCount => prevCount + 1);
}, []); // 现在不需要依赖count了

2. 过时闭包

javascript 复制代码
function Timer() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // 这里总是使用初始的count值
    }, 1000);
    
    return () => clearInterval(intervalId);
  }, []); // 缺少count依赖
}

解决方案是使用函数式更新或者正确添加依赖:

javascript 复制代码
// 方案1:使用函数式更新
setCount(prevCount => prevCount + 1);

// 方案2:正确添加依赖
}, [count]);

总结

useEffect的第二个参数是React性能优化和正确性的关键。通过合理使用依赖数组,我们可以精确控制副作用的执行时机,避免不必要的渲染和性能浪费。

记住这几个要点:

  • 空数组[]表示只在挂载时执行
  • 不传参数表示每次渲染都执行
  • 传入依赖项表示只有当依赖变化时执行
  • 遵守ESLint规则,不要轻易禁用

希望我的这些经验能帮助大家更好地理解和使用useEffect。如果有任何问题,欢迎在评论区讨论。 Happy coding!

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
gongzemin8 分钟前
使用阿里云ECS部署前端应用
前端·vue.js·后端
用户41180034153419 分钟前
Flutter课题汇报
前端
环信13 分钟前
实战教程|快速上线音视频通话:手把手教你实现呼叫与接听全流程
前端
Dgua13 分钟前
✨TypeScript快速入门第一篇:从基础到 any、unknown、never 的实战解析
前端
海云前端114 分钟前
Vue3 大屏项目投屏功能开发:多显示器适配实践
前端
技术小丁29 分钟前
使用 HTML + JavaScript 实现酒店订房日期选择器(附完整源码)
前端·javascript
hashiqimiya31 分钟前
harmonyos的鸿蒙的跳转页面的部署
开发语言·前端·javascript
向日葵同志4433041 分钟前
使用@univerjs纯前端渲染excel, 显示图片、链接、样式
前端·react.js·excel
闭着眼睛学算法1 小时前
【双机位A卷】华为OD笔试之【排序】双机位A-银行插队【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·javascript·c++·python·算法·华为od
可别3901 小时前
使用Worker打包报错
前端·vue.js