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

简介: 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

目录
相关文章
|
6月前
|
前端开发
轻松掌握 React Hooks:简化状态与副作用管理
轻松掌握 React Hooks:简化状态与副作用管理
234 80
|
6月前
|
前端开发
React Hooks数据获取:避免内存泄漏的实战指南
React Hooks数据获取:避免内存泄漏的实战指南
|
2月前
|
缓存 前端开发 JavaScript
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
🌟蒋星熠Jaxonic,前端探索者。专注React Hooks深度实践,从原理到实战,分享状态管理、性能优化与自定义Hook精髓。助力开发者掌握函数组件的无限可能,共赴技术星辰大海!
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
|
5月前
|
前端开发 JavaScript 编译器
React编程新手入门实践教程
本书深入解析React核心思想与设计哲学,涵盖组件化思维、虚拟DOM原理及JSX本质,探讨函数组件与类组件特性,详解状态管理、生命周期控制及事件处理机制,帮助开发者掌握高效构建用户界面的技巧。
189 1
|
7月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
288 68
|
7月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
264 67
|
7月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
301 62
|
10月前
|
移动开发 前端开发 API
React 音频播放器组件 Audio Player
本文介绍如何使用React创建音频播放器组件,涵盖核心功能如播放/暂停、进度条、音量控制和时间显示。通过HTML5 `&lt;audio&gt;` 元素和React的声明式状态管理,实现交互式音频播放。常见问题包括控件不响应、进度条无法更新和音量控制失灵,并提供解决方案。此外,还讨论了浏览器兼容性、异步错误处理和性能优化等易错点及避免方法。
755 123
|
9月前
|
前端开发 JavaScript
除了使用Route组件,React Router还有其他方式处理404错误页面吗
除了使用Route组件,React Router还有其他方式处理404错误页面吗
259 58
|
9月前
|
前端开发
React 中高阶组件的原理是什么?
React 中高阶组件的原理是什么?
244 57