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

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 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

目录
相关文章
|
11天前
|
前端开发 JavaScript
深入探索React Hooks:从useState到useEffect
深入探索React Hooks:从useState到useEffect
|
21天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
28 2
|
25天前
|
前端开发 开发者
React 提供的其他重要 Hooks
【10月更文挑战第20天】React 提供了一系列强大的 Hooks,除了 `useRef` 之外,还有许多其他重要的 Hooks,它们共同构成了函数式组件开发的基础。
35 6
|
1月前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
14天前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
45 9
|
1月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
1月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
1月前
|
前端开发 数据管理 编译器
引领前端未来:React 19的重大更新与实战指南🚀
React 19 即将发布,带来一系列革命性的新功能,旨在简化开发过程并显著提升性能。本文介绍了 React 19 的核心功能,如自动优化重新渲染的 React 编译器、加速初始加载的服务器组件、简化表单处理的 Actions、无缝集成的 Web 组件,以及文档元数据的直接管理。这些新功能通过自动化、优化和增强用户体验,帮助开发者构建更高效的 Web 应用程序。
101 1
引领前端未来:React 19的重大更新与实战指南🚀
|
19天前
|
前端开发 JavaScript Android开发
前端框架趋势:React Native在跨平台开发中的优势与挑战
【10月更文挑战第27天】React Native 是跨平台开发领域的佼佼者,凭借其独特的跨平台能力和高效的开发体验,成为许多开发者的首选。本文探讨了 React Native 的优势与挑战,包括跨平台开发能力、原生组件渲染、性能优化及调试复杂性等问题,并通过代码示例展示了其实际应用。
45 2
|
21天前
|
前端开发 JavaScript 开发者
React与Vue:前端框架的巅峰对决与选择策略
【10月更文挑战第23天】React与Vue:前端框架的巅峰对决与选择策略