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

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

目录
相关文章
|
20天前
|
前端开发 JavaScript
深入理解并实践React Hooks —— useEffect与useState
深入理解并实践React Hooks —— useEffect与useState
84 1
|
9天前
|
前端开发
React中函数式Hooks之useEffect的使用
本文通过示例代码讲解了React中`useEffect` Hook的用法,包括模拟生命周期、监听状态和清理资源。
24 2
React中函数式Hooks之useEffect的使用
|
16天前
|
Web App开发 前端开发 测试技术
react18基础教程系列--安装环境及packagejson文件分析
react18基础教程系列--安装环境及packagejson文件分析
|
9天前
|
缓存 前端开发
React中函数式Hooks之memo、useCallback的使用以及useMemo、useCallback的区别
React中的`memo`是高阶组件,类似于类组件的`PureComponent`,用于避免不必要的渲染。`useCallback` Hook 用于缓存函数,避免在每次渲染时都创建新的函数实例。`memo`可以接收一个比较函数作为第二个参数,以确定是否需要重新渲染组件。`useMemo`用于缓存计算结果,避免重复计算。两者都可以用来优化性能,但适用场景不同:`memo`用于组件,`useMemo`和`useCallback`用于值和函数的缓存。
30 1
|
9天前
|
前端开发
React中函数式Hooks之useState的使用
本文介绍了React中函数式组件的Hooks——`useState`的使用方法。`useState`允许在函数式组件中使用状态,它返回一个数组,其中包含当前状态的值和更新该状态的函数。文章通过示例代码展示了如何声明状态变量和更新状态变量,包括对数值和对象状态的更新。此外,还展示了如何通过点击按钮触发状态更新,实现交互功能。
22 1
|
2月前
|
前端开发 JavaScript API
|
2月前
|
前端开发 JavaScript
介绍React中的useEffect
【8月更文挑战第6天】介绍React中的useEffect
30 2
|
2月前
|
前端开发 开发者
介绍React的useState
【8月更文挑战第6天】 介绍React的useState
33 1
|
2月前
|
前端开发 JavaScript UED
React 基础与实践 | 青训营笔记
React 基础与实践 | 青训营笔记
44 0
|
3月前
|
前端开发 JavaScript Java
React 速通笔记
【7月更文挑战第17天】
40 1

热门文章

最新文章

下一篇
无影云桌面