当前位置: 首页 > news >正文

useEffect

下面,我们来系统的梳理关于 React useEffect Hook 的基本知识点:


一、useEffect 基础概念

1.1 什么是副作用(Side Effect)?

在 React 中,副作用是指与组件渲染结果无关的操作,例如:

  • 数据获取(API 请求)
  • 订阅事件(WebSocket、DOM 事件)
  • 手动修改 DOM
  • 设置定时器
  • 日志记录
  • 全局状态管理

1.2 useEffect 的作用

useEffect 是 React 提供的 Hook,用于在函数组件中执行副作用操作。它取代了类组件中的生命周期方法:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

1.3 基本语法

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

二、useEffect 核心原理

2.1 执行时机

组件渲染
渲染DOM
执行useEffect
浏览器绘制
  • 渲染后执行:useEffect 在浏览器完成布局与绘制之后执行
  • 异步执行:不会阻塞浏览器渲染
  • 顺序执行:多个 useEffect 按声明顺序依次执行

2.2 依赖数组机制

依赖数组控制 effect 的执行时机:

依赖数组执行时机
无依赖数组每次渲染后执行
空数组 []仅组件挂载后执行一次
有依赖项 [a, b]当 a 或 b 变化时执行

2.3 清理机制

Effect执行清理函数下一次Effect上次的清理函数(如果有)执行清理执行新effect返回新的清理函数Effect执行清理函数下一次Effect

三、useEffect 使用模式

3.1 无依赖数组(每次渲染后执行)

useEffect(() => {console.log('每次渲染后都会执行');
});

3.2 空依赖数组(仅挂载时执行)

useEffect(() => {console.log('仅在组件挂载后执行一次');return () => {console.log('组件卸载时执行清理');};
}, []);

3.3 有依赖数组(依赖变化时执行)

const [count, setCount] = useState(0);useEffect(() => {console.log(`count变化时执行: ${count}`);
}, [count]); // 依赖count

3.4 清理函数

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

四、useEffect 高级用法

4.1 多个 useEffect 的使用

function UserProfile({ userId }) {const [user, setUser] = useState(null);// 获取用户数据useEffect(() => {fetchUser(userId).then(setUser);}, [userId]);// 更新文档标题useEffect(() => {document.title = user ? `${user.name}的资料` : '加载中...';}, [user]);// ...
}

4.2 在 effect 中获取最新状态

const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {console.log(count); // 总是捕获初始值(闭包问题)}, 1000);return () => clearInterval(timer);
}, []);

解决方案

// 方案1: 使用函数式更新(不适用于所有场景)
setCount(c => c + 1);// 方案2: 使用ref保存最新值
const countRef = useRef(count);
countRef.current = count;// 方案3: 添加依赖(可能触发频繁执行)
useEffect(() => {// ...
}, [count]);

4.3 异步 effect

useEffect(() => {let isMounted = true; // 防止组件卸载后设置状态const fetchData = async () => {const data = await fetch('/api/data');if (isMounted) {setData(data);}};fetchData();return () => {isMounted = false;};
}, []);

五、性能优化策略

5.1 避免不必要的 effect 执行

const [user, setUser] = useState(null);
const [profile, setProfile] = useState(null);// 不推荐:依赖整个user对象
useEffect(() => {if (user) {fetchProfile(user.id);}
}, [user]);// 推荐:只依赖必要属性
useEffect(() => {if (user?.id) {fetchProfile(user.id);}
}, [user?.id]);

5.2 使用 useCallback 优化函数依赖

const fetchData = useCallback(async () => {const data = await fetch('/api/data');setData(data);
}, []);useEffect(() => {fetchData();
}, [fetchData]);

5.3 使用 useMemo 优化对象依赖

const config = useMemo(() => ({color: theme.color,size: 'large'
}), [theme.color]);useEffect(() => {applyConfig(config);
}, [config]);

六、常见问题与解决方案

6.1 无限循环问题

问题代码

const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1); // 触发重新渲染
}, [count]); // 依赖count变化

解决方案

  1. 检查是否需要更新状态
  2. 使用函数式更新避免依赖
useEffect(() => {setCount(c => c + 1); // 不依赖count
}, []);

6.2 依赖数组不完整

问题:ESLint 警告依赖不完整

解决方案

  1. 添加所有在 effect 中使用的依赖项
  2. 如果确实不需要依赖变化时执行,使用 // eslint-disable-next-line(慎用)
  3. 重构代码避免使用外部变量

6.3 清理函数执行时机

问题:清理函数在每次依赖变化时都会执行

解决方案

useEffect(() => {// 设置订阅const subscription = dataSource.subscribe();return () => {// 每次依赖变化时都会执行清理subscription.unsubscribe();};
}, [dataSource]); // 依赖变化触发清理

七、useEffect 最佳实践

7.1 副作用分离原则

// 数据获取
useEffect(() => { /* ... */ }, [url]);// 事件订阅
useEffect(() => { /* ... */ }, []);// DOM操作
useEffect(() => { /* ... */ }, [element]);

7.2 自定义 Hook 封装

function useWindowSize() {const [size, setSize] = useState({width: window.innerWidth,height: window.innerHeight});useEffect(() => {const handleResize = () => {setSize({width: window.innerWidth,height: window.innerHeight});};window.addEventListener('resize', handleResize);return () => window.removeEventListener('resize', handleResize);}, []);return size;
}

7.3 使用 useEffectEvent(未来API)

// 实验性API(React 18+)
import { useEffect, useEffectEvent } from 'react';function ChatRoom({ roomId }) {const onMessage = useEffectEvent((message) => {// 可以访问roomId但不需要声明依赖showNotification(roomId, message);});useEffect(() => {const connection = connect(roomId);connection.on('message', onMessage);return () => connection.disconnect();}, [roomId]); // 只依赖roomId
}

八、案例

8.1 数据获取与取消

function ProductDetails({ productId }) {const [product, setProduct] = useState(null);const [error, setError] = useState(null);useEffect(() => {let isCancelled = false;const fetchData = async () => {try {const data = await fetchProduct(productId);if (!isCancelled) {setProduct(data);}} catch (err) {if (!isCancelled) {setError(err);}}};fetchData();return () => {isCancelled = true;};}, [productId]);// ...
}

8.2 滚动位置恢复

function ScrollPosition({ page }) {const scrollRef = useRef(null);// 保存滚动位置useEffect(() => {const handleScroll = () => {sessionStorage.setItem(`scroll-${page}`, scrollRef.current.scrollTop);};scrollRef.current.addEventListener('scroll', handleScroll);return () => {scrollRef.current.removeEventListener('scroll', handleScroll);};}, [page]);// 恢复滚动位置useEffect(() => {const savedPosition = sessionStorage.getItem(`scroll-${page}`);if (savedPosition) {scrollRef.current.scrollTop = Number(savedPosition);}}, [page]);return <div ref={scrollRef} className="scroll-container">...</div>;
}

8.3 动画控制

function FadeInElement({ children }) {const ref = useRef(null);useEffect(() => {const element = ref.current;// 初始状态element.style.opacity = '0';element.style.transform = 'translateY(20px)';element.style.transition = 'opacity 0.5s, transform 0.5s';// 触发动画requestAnimationFrame(() => {element.style.opacity = '1';element.style.transform = 'translateY(0)';});return () => {// 清理样式element.style.opacity = '';element.style.transform = '';element.style.transition = '';};}, []);return <div ref={ref}>{children}</div>;
}

九、useEffect 与类组件生命周期对比

类组件生命周期useEffect 等效实现说明
componentDidMountuseEffect(fn, [])空依赖数组
componentDidUpdateuseEffect(fn)useEffect(fn, [dep])无依赖或有依赖数组
componentWillUnmountuseEffect(() => { return cleanupFn }, [])返回清理函数
shouldComponentUpdate无直接等效,使用 React.memouseMemo优化渲染

十、总结与最佳实践

10.1 核心原则

  1. 副作用分离:每个 effect 只做一件事
  2. 依赖精确:确保依赖数组包含所有变化的值
  3. 清理资源:返回清理函数避免内存泄漏
  4. 避免阻塞:不要在 effect 中执行同步耗时操作

10.2 最佳实践清单

  • ✅ 使用多个 useEffect 分离不同副作用
  • ✅ 为每个 effect 添加精确的依赖数组
  • ✅ 返回清理函数取消订阅/定时器
  • ✅ 使用自定义 Hook 复用副作用逻辑
  • ✅ 避免在 effect 中直接修改 DOM(使用 ref)
  • ✅ 使用 useCallback/useMemo 优化依赖项
  • ✅ 处理异步操作的取消和清理

10.3 常见错误避免

  • ❌ 遗漏依赖导致过时闭包
  • ❌ 忘记清理订阅/定时器
  • ❌ 在 effect 中执行阻塞渲染的操作
  • ❌ 过度使用无依赖 effect 导致性能问题
  • ❌ 在渲染期间设置状态导致无限循环
http://www.dtcms.com/a/295045.html

相关文章:

  • Java异常处理核心原理与最佳实践
  • 数据驱动未来:构建强大AI系统的基石
  • QPixmap::scaled参数说明
  • 床上肢体康复机器人的机械结构设计cad【7张】三维图+设计说明书
  • 1、黑马点评复盘(短信登录-Session或Redis实现)
  • pytest简单使用和生成测试报告
  • FCW(Front Collision Warning)前碰撞预警功能介绍
  • 借助DataStream和多路复用实现可观察性
  • mybatis条件语句的查询与注解的使用以及mybatis与servelet结合查询
  • 数据结构系列之AVL树
  • 主要科技公司与新创公司 AI Agent 进展调研
  • Nginx 日志分析与慢请求排查
  • Symantec sep配置自定义yara规则
  • 背包九讲 详细解析与 C++ 实现
  • 不一样的Mysql安装方式
  • (8)Step 7 实现泵组主备切换与PID变频调节(压力——频率)
  • LangChain面试内容整理-知识点28:LangChain部署实践
  • 【JavaSE】正则表达式学习笔记
  • 二、计算机网络技术——第4章:网络层
  • 跟著Qcadoo MES系统学习产品设计001
  • 从订单簿到AMM:一场去中心化交易所的技术革命
  • 彻底掌握双列集合——Map接口以及实现类和常用API及其底层原理
  • 1688商品数据采集的应用行业与接入方式
  • 人工智能之数学基础:事件间的运算
  • JVM、Dalvik、ART垃圾回收机制
  • OpenLayers 快速入门(八)事件系统
  • java基础(the 15th day)
  • freelancer是什么用工模式?有什么好处和坏处呢?
  • Log4j2漏洞vul-hub通关教程
  • 根据图片的r值来进行透明剔除