大家好,我是小杨,一名有近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
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!