react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱

useEffect 、 useState 会创建闭包,在某些场景下会导致意外的行为,这些异常现象称为 react Hooks 的闭包陷阱。

useState 闭包陷阱

setCount 后无法取到 count 的最新值

import { useState } from "react";

export default function Father() {
  const [count, setCount] = useState(0);
  const add = () => {
    setCount(count + 1);
    console.log("add函数内count的值为", count);
  };

  console.log("add函数外count的值为", count);
  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
    </div>
  );
}

点击按钮后执行结果为

add函数内count的值为 0
add函数外count的值为 1

原因解析


useState 修改响应式变量的值是异步执行的,即在 add函数 执行期间,count 并没有被改变!

待 useState 完成对响应式变量 count 的修改后,会触发组件重新渲染(组件函数会再次执行),因此时 count 已修改,所以会打印 add函数外count的值为 1

解决方案

不要在 setCount 后,拿 count 做其他操作!

多次 setCount 只会执行最后一次

import { useState } from "react";

export default function Father() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 2);
    setCount(count + 1);
  };

  console.log("count的值为", count);
  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
    </div>
  );
}

点击按钮后执行结果为

count的值为 1

原因解析

在一个渲染周期内,对同一响应式变量进行多次修改时,仅最后一次修改有效!(为了提升渲染性能)

解决方案

setCount 改传入一个函数(实现每一次 count 都是取的修改后的新值!)

import { useState } from "react";

export default function Father() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount((count) => count + 2);
    setCount((count) => count + 1);
  };

  console.log("count的值为", count);
  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
    </div>
  );
}

最佳实践

setCount 的参数,都用函数的形式传入!

useEffect 闭包陷阱

import { useState, useEffect } from "react";

export default function Father() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    setInterval(() => {
      console.log(`setInterval中 count 的值为: ${count}`);
    }, 1000);
  }, []);

  console.log("count的值为", count);
  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
    </div>
  );
}

点击按钮后执行结果为

count的值为 1
setInterval中 count 的值为: 0

原因解析


依赖为 [] 的 useEffect 仅在组件初次渲染时执行一次,所以 setInterval中 count 的值为 0


点击 + 按钮后,count 变为1,并触发组件重新渲染,此时会生成新的state对象快照,但 setInterval 内一直取的组件初次渲染时创建的state对象的快照,所以一直是0,不会变化!

解决方案

  • 依赖项改为 count
  • 每次组件重新渲染时,都清除计时器(否则每次 count 变化,都会生成一个新的计时器)
import { useState, useEffect } from "react";

export default function Father() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(`setInterval中 count 的值为: ${count}`);
    }, 1000);
    return () => clearInterval(timer);
  }, [count]);

  console.log("count的值为", count);
  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
    </div>
  );
}

useCallback 闭包陷阱

Father.jsx

import { useState } from "react";
import Child1 from "./Child1.jsx";

export default function Father() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <span>{count}</span>
      <button onClick={add}> + </button>
      <Child1 count={count} />
    </div>
  );
}

Child1.jsx

import { useCallback } from "react";

function Child1({ count }) {
  let log = useCallback(() => {
    console.log("useCallback中count的值为: ", count);
  }, []);

  return (
    <div style={{ border: "1px solid", padding: "10px", margin: "10px" }}>
      <h1>子组件1</h1>
      <p>count: {count}</p>
      <button onClick={() => log()}> 打印 </button>
    </div>
  );
}

export default Child1;

先点击父组件 + 号,再点击子组件里的 打印 按钮,结果为

原因解析

useCallback 依赖为 [] 时,取到是子组件初次渲染时获取到的来自父组件的 count 值 0 ,当父组件的 count 变化后,useCallback 不会重新取值,所以 count 不会变化

解决方案

将 useCallback 的依赖为 count

let log = useCallback(() => {
   console.log("useCallback中count的值为: ", count);
 }, [count]);

自测题

第1题

点击 更新 按钮后,页面 count 和控制台打印的值

import { useState } from "react";

export default function Father() {
  const [count, setcount] = useState(10);

  function updateCount() {
    setcount(count + 1);
    console.log("1-", count);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={updateCount}>更新</button>
    </div>
  );
}

答案

页面的 count 为 11 
控制台打印 1- 10

第2题

点击 更新 按钮后,页面 count 和控制台打印的值

import { useState } from "react";

export default function Father() {
  const [count, setcount] = useState(10);

  function updateCount() {
    setcount(count + 1);
    console.log("1-", count);
    setcount(count + 1);
    console.log("2-", count);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={updateCount}>更新</button>
    </div>
  );
}
页面的 count 为 11 
控制台打印 
1- 10  
2- 10

第3题

点击 更新 按钮后,页面 count 和控制台打印的值

import { useState } from "react";

export default function Father() {
  const [count, setcount] = useState(10);

  function updateCount() {
    setcount(count + 1);
    console.log("1-", count);
    setcount((count) => count + 1);
    console.log("2-", count);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={updateCount}>更新</button>
    </div>
  );
}

答案

页面的 count 为 12
控制台打印 
1- 10  
2- 10

第4题

点击 更新 按钮后,页面 count 和控制台打印的值

import { useState } from "react";

export default function Father() {
  const [count, setcount] = useState(10);

  function updateCount() {
    setTimeout(() => {
      setcount(count + 1);
      setcount(count + 1);
      console.log("1-", count);
      setcount(count + 1);
      setcount(count + 1);
      console.log("2-", count);
    });
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={updateCount}>更新</button>
    </div>
  );
}

答案

页面的 count 为 11
控制台打印 
1- 10  
2- 10

目录
相关文章
|
1月前
|
前端开发
深入探索React Hooks:从useState到useEffect
深入探索React Hooks:从useState到useEffect
25 3
|
1月前
|
前端开发 JavaScript
深入探索React Hooks:从useState到useEffect
深入探索React Hooks:从useState到useEffect
|
3月前
|
前端开发
React中函数式Hooks之useEffect的使用
本文通过示例代码讲解了React中`useEffect` Hook的用法,包括模拟生命周期、监听状态和清理资源。
59 2
React中函数式Hooks之useEffect的使用
|
2月前
|
前端开发 JavaScript CDN
React 教程
10月更文挑战第6天
50 3
|
2月前
|
存储 前端开发 JavaScript
React useState 和 useRef 的区别
本文介绍了 React 中 `useState` 和 `useRef` 这两个重要 Hook 的区别和使用场景。`useState` 用于管理状态并在状态变化时重新渲染组件,适用于表单输入、显示/隐藏组件、动态样式等场景。`useRef` 则用于在渲染之间保持可变值而不触发重新渲染,适用于访问 DOM 元素、存储定时器 ID 等场景。文章还提供了具体的代码示例,帮助读者更好地理解和应用这两个 Hook。
61 0
|
3月前
|
缓存 前端开发
React中函数式Hooks之memo、useCallback的使用以及useMemo、useCallback的区别
React中的`memo`是高阶组件,类似于类组件的`PureComponent`,用于避免不必要的渲染。`useCallback` Hook 用于缓存函数,避免在每次渲染时都创建新的函数实例。`memo`可以接收一个比较函数作为第二个参数,以确定是否需要重新渲染组件。`useMemo`用于缓存计算结果,避免重复计算。两者都可以用来优化性能,但适用场景不同:`memo`用于组件,`useMemo`和`useCallback`用于值和函数的缓存。
106 1
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
78 9
|
2月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
1月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
114 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
1月前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
36 1