解密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

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

相关推荐
江城开朗的豌豆4 小时前
React Hooks必杀技:前端工程师小杨带你玩转常用API!
前端·javascript·react.js
江城开朗的豌豆4 小时前
Redux状态更新:异步还是同步?
前端·javascript·react.js
前端小巷子4 小时前
Vue 项目性能优化实战
前端·vue.js·面试
Aphasia3114 小时前
useEffect 中Clean up 函数的执行机制
前端·react.js·面试
xw54 小时前
我的后台管理项目报Error: spawn …esbuild.exe ENOENT了
前端
夏小花花4 小时前
关于牙科、挂号、医生类小程序或管理系统项目 项目包含微信小程序和pc端两部分
前端·javascript·vue.js·微信小程序·小程序
IT_陈寒4 小时前
SpringBoot 3.2 踩坑实录:这5个‘自动配置’的坑,让我加班到凌晨三点!
前端·人工智能·后端
OEC小胖胖5 小时前
代码质量保障:使用Jest和React Testing Library进行单元测试
前端·react.js·单元测试·前端框架·web
獨孤殤5 小时前
Flutter + Web:深度解析双向通信的混合应用开发实践
前端·flutter·vue