React性能优化三剑客:useEffect、useMemo与useCallback实战手册

​废话不多说,直接上干货,兄弟们,注意了,我要开始装逼了:​

一、useEffect:副作用管理

核心作用

处理与渲染无关的副作用(如数据获取、订阅事件、定时器、DOM 操作等),并支持清理逻辑。

基本语法

scss 复制代码
useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理逻辑(可选)
  };
}, [依赖项]);

使用场景

  1. 数据获取

    scss 复制代码
    useEffect(() => {
      fetch("/api/data")
        .then(res => setData(res));
    }, []); // 仅在挂载时获取
  2. 事件监听

    javascript 复制代码
    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth);
      window.addEventListener("resize", handleResize);
      return () => window.removeEventListener("resize", handleResize);
    }, []); // 清理监听器
  3. 定时器

    scss 复制代码
    useEffect(() => {
      const timer = setInterval(() => setCount(c => c + 1), 1000);
      return () => clearInterval(timer);
    }, []); // 清理定时器

最佳实践

  • 依赖数组:精确控制执行时机,避免遗漏依赖导致闭包问题。
  • 清理函数:移除订阅、清除定时器,防止内存泄漏。
  • 避免无限循环 :若副作用中修改状态,使用函数式更新(如 setCount(prev => prev + 1))。

二、useMemo:计算结果缓存

核心作用

缓存复杂计算结果,避免重复执行高开销操作(如排序、过滤、深拷贝)。

基本语法

scss 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

使用场景

  1. 复杂计算

    ini 复制代码
    const filteredList = useMemo(() => 
      list.filter(item => item.price > 100), 
      [list] // 仅当 list 变化时重新计算
    );
  2. 避免重复渲染

    ini 复制代码
    const userInfo = useMemo(() => ({ name, age }), [name, age]);
    return <Child user={userInfo} />; // 稳定引用,避免子组件重渲染

最佳实践

  • 依赖项完整:包含所有影响计算结果的变量。
  • 避免滥用:简单计算无需缓存,直接执行即可。
  • **配合 React.memo**:缓存子组件,提升渲染性能。

三、useCallback:函数引用缓存

核心作用

缓存函数引用,避免因父组件重新渲染导致子组件不必要的重渲染。

基本语法

ini 复制代码
const memoizedFn = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

使用场景

  1. 传递回调函数

    ini 复制代码
    const handleClick = useCallback(() => {
      console.log("Clicked");
    }, []);
    return <Button onClick={handleClick} />;
  2. ​**配合 useEffect**​

    scss 复制代码
    const fetchData = useCallback(async () => {
      const data = await fetch("/api");
      setData(data);
    }, []);
    useEffect(() => fetchData(), [fetchData]);

最佳实践

  • 依赖项精准:确保函数内部使用的变量均包含在依赖数组中。
  • 避免过度优化 :仅在需要稳定引用时使用(如传递给 React.memo 子组件)。

四、三者的核心区别

Hook 核心用途 返回值 典型场景
useEffect 处理副作用(数据获取、订阅) 无(返回清理函数) API 请求、事件监听
useMemo 缓存计算结果 计算后的值 复杂计算、避免重复渲染
useCallback 缓存函数引用 函数 传递回调、优化子组件渲染

五、useEffect 最佳实践与闭包陷阱规避

1. 依赖项管理

  • 核心原则 ​:依赖数组必须包含所有在 effect 中使用的 ​外部变量​(包括 state、props、上下文等)。

    scss 复制代码
    // 错误示例:未包含依赖项,导致闭包捕获旧值
    const [count, setCount] = useState(0);
    useEffect(() => {
      const timer = setInterval(() => {
        console.log(count); // 始终输出初始值 0
      }, 1000);
      return () => clearInterval(timer);
    }, []); // ❌ 依赖数组为空
    
    // 正确做法:将 count 加入依赖数组
    useEffect(() => {
      const timer = setInterval(() => {
        console.log(count); // 实时输出最新值
      }, 1000);
      return () => clearInterval(timer);
    }, [count]); // ✅

    问题根源 ​:未声明依赖项时,effect 仅在挂载时执行一次,闭包中捕获的 count 是初始值。

2. 清理函数

  • 场景​:定时器、事件监听、DOM 操作等需在组件卸载或依赖变化时清理。

    javascript 复制代码
    useEffect(() => {
      const handleResize = () => console.log("窗口大小变化");
      window.addEventListener("resize", handleResize);
      return () => window.removeEventListener("resize", handleResize); // ✅ 清理监听器
    }, []);

3. 避免无限循环

  • 问题​:effect 中修改状态且依赖该状态。

    scss 复制代码
    // 错误示例:依赖项包含 state,导致循环触发
    const [data, setData] = useState([]);
    useEffect(() => {
      fetchData().then((res) => setData(res)); // 触发重新渲染 → effect 重新执行
    }, [data]); // ❌

    解决方案​:移除冗余依赖,或使用函数式更新:

    scss 复制代码
    useEffect(() => {
      fetchData().then((res) => setData((prev) => [...prev, ...res])); // ✅ 无依赖
    }, []);

4. 复杂异步操作

  • 最佳实践 ​:在 effect 内部定义异步函数,避免直接 async 修饰回调。

    ini 复制代码
    useEffect(() => {
      let isMounted = true; // 防止卸载后更新状态
      const fetchData = async () => {
        const res = await api.getData();
        if (isMounted) setData(res);
      };
      fetchData();
      return () => { isMounted = false; }; // 清理标志位
    }, []);

六、useMemouseCallback 的真正使用场景

1. useMemo:缓存计算结果

  • 适用场景​:

    • 高开销计算:如大数据排序、复杂公式、深拷贝。
    • 派生状态:根据 state/props 生成新对象或数组。
    • DOM 节点引用 :如 useRef 存储 DOM 元素(需配合 useLayoutEffect)。
  • 避免滥用​:

    • 简单计算(如 a + b)无需缓存。
    • 频繁变化的依赖项(如 useMemo 依赖项变化频繁时反而降低性能)。
    ini 复制代码
    // 正确使用:缓存复杂计算
    const filteredData = useMemo(() => {
      return data.filter(item => item.price > 100);
    }, [data]);
    
    // 错误使用:缓存简单值
    const simpleValue = useMemo(() => 42, []); // ❌ 无意义

2. useCallback:缓存函数引用

  • 适用场景​:

    • 传递回调给子组件 :子组件使用 React.memo 时,避免因父组件重渲染导致子组件无效更新。
    • 依赖外部变量的函数:如事件处理器中依赖 state/props。
  • 避免滥用​:

    • 仅在需要稳定引用时使用(如传递给 useEffect 或子组件)。
    • 内部函数无需缓存(如仅在组件内部使用的函数)。
    scss 复制代码
    // 正确使用:缓存事件处理器
    const handleClick = useCallback(() => {
      console.log("Clicked");
    }, []);
    
    // 错误使用:缓存内部函数
    const internalFn = useCallback(() => {
      // 仅在组件内部使用,无需缓存
    }, []);

七、避免过度优化的关键原则

1. 性能问题驱动优化

  • 先定位瓶颈 :使用 React DevTools 的 Profiler 分析组件渲染开销。
  • 针对性优化 :仅在渲染耗时或计算密集型场景使用 useMemo/useCallback

2. 依赖项管理

  • 完整声明 :使用 ESLint 的 react-hooks/exhaustive-deps 规则强制检查。
  • 避免冗余依赖 :如 useMemo 依赖项中包含未使用的变量。

3. 简单场景优先

  • 优先使用普通变量 :如 const value = someData; 而非 useMemo(() => someData, [])
  • 函数内部使用:无需缓存仅在组件内部调用的函数。

八、实战案例对比

场景:商品列表筛选

  • 未优化版本​(频繁重渲染):

    ini 复制代码
    const ProductList = ({ products, filter }) => {
      const filtered = products.filter(/* 复杂逻辑 */);
      return <List items={filtered} />;
    };
  • 优化后版本​:

    ini 复制代码
    const ProductList = ({ products, filter }) => {
      const filtered = useMemo(() => {
        return products.filter(/* 复杂逻辑 */);
      }, [products, filter]); // 仅当数据或筛选条件变化时重新计算
      return <List items={filtered} />;
    };

九、总结

Hook 核心用途 避坑指南
useEffect 副作用管理(数据获取、订阅) 依赖项完整、清理函数、避免循环
useMemo 缓存计算结果 仅高开销计算、避免简单值缓存
useCallback 缓存函数引用 仅传递给 memo 子组件、避免内部函数

关键口诀 ​:

​"无必要,不优化;有性能问题,再针对性解决"​。过度使用 Hooks 反而会增加代码复杂度和内存开销。

相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell5 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清5 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木5 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076605 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声5 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易5 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得06 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion6 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计