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

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

目录
相关文章
|
9天前
|
前端开发 JavaScript
React_函数式Hooks和Class比较优缺点
React Hooks与Class组件都能返回JSX并接收props,但Hooks无`this`指向问题,用`useEffect`模拟生命周期,`memo`优化性能,状态更新用`useState`;Class组件通过生命周期方法、`PureComponent`或`shouldComponentUpdate`优化,状态用`this.state`和`this.setState`管理。
20 1
React_函数式Hooks和Class比较优缺点
|
4天前
|
存储 缓存 前端开发
使用React hooks,些许又多了不少摸鱼时间
该文章详细讲解了React Hooks的各种用法,包括useState、useEffect、useContext等基础Hooks,以及自定义Hooks的创建,并通过实际示例展示了如何利用Hooks简化组件状态管理和副作用操作,从而提高开发效率。
|
7天前
|
前端开发 JavaScript API
深入探索React Hooks与状态管理
深入探索React Hooks与状态管理
24 2
|
8天前
|
前端开发
React使用hooks遇到的坑_state中的某几个属性数据变成了空字符
本文讨论了在React使用hooks时遇到的一个问题:state中的某些属性数据变成了空字符。作者通过在修改函数中重新解构赋值来获取最新的state值,解决了因数据更新不及时导致的问题。
20 0
|
2月前
|
前端开发 JavaScript UED
React 基础与实践 | 青训营笔记
React 基础与实践 | 青训营笔记
44 0
|
3月前
|
前端开发 JavaScript Java
React 速通笔记
【7月更文挑战第17天】
38 1
|
前端开发
前端学习笔记202305学习笔记第二十九天-React keep alive原理之2
前端学习笔记202305学习笔记第二十九天-React keep alive原理之2
71 0
|
前端开发
前端学习笔记202306学习笔记第四十八天-react-admin marmelab之8
前端学习笔记202306学习笔记第四十八天-react-admin marmelab之7
48 0
|
5月前
|
前端开发 JavaScript
前端知识笔记(二十六)———React如何像Vue一样将css和js写在同一文件
前端知识笔记(二十六)———React如何像Vue一样将css和js写在同一文件
59 1
|
11月前
|
前端开发
前端笔记:React的form表单全部置空或者某个操作框置空的做法
在React框架前端开发中,经常会有弹出框的开发,涉及到弹出框,难免就会有表单。一般在关闭弹出框或者对表单联动时,往往都需要考虑对表单进行置空操作了。
87 0