React中的闭包陷阱以及如何使用useState姿势(I)

简介: React中的闭包陷阱以及如何使用useState姿势

16.png


问题的引出

import { useEffect, useState } from'react';
exportdefaultfunction App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => {
      setCount(count + 1);
    }, 1500);
  }, []);
  useEffect(() => {
    setInterval(() => {
      console.log(count);
    }, 1500);
  }, []);
  return<div>Hello world</div>;
}

控制台会输出什么?

1.png

如上图所示,输出的是0,而不是我们所期待的0, 1, 2, 3, …

这就是闭包陷阱;


分析

1.组件是一个Fiber节点

2.每个Fiber节点都有一个属性叫做memorizedState,它是一个链表结构

3.组件的每个Hook都对应于memorizedState链表的一个节点,它们从对应的节点访问相对应的值。

2.png

3.png

上例中有三个Hook,每个Hook对应链表中的一个节点memorizedState

4.png

然后每个Hook访问自己的memorizedState以完成自己的逻辑。


Hooks的实现

Hook 有两个阶段:挂载和更新。

mount函数在第一次创建Hook时执行,之后每次更新Hook时执行update函数。

5.png

这里是useEffect实现:

6.png


Hooks 如何处理 deps?

这里要注意deps参数的处理:如果deps是未定义的,则将deps其视为null

7.png

然后它将新传递的deps与之前memorizedState中的deps进行比较:如果两者相等,则直接使用先前给定的函数,否则,创建一个新函数。

8.png

比较两个dep是否相等的逻辑非常简单:如果先前的dep为null,则直接返回false,也就是说,它们不相等;否则,会遍历并进行比较。

9.png

所以我们可以得到三个结论:(重点)

如果useEffect的deps参数为undefined或null,则将在每次渲染中重新创建并执行回调函数(产生无限循环)。

如果它是一个空数组,那么回调将只执行一次。

否则,它将比较deps数组中的每个元素是否已更改,以决定是否执行该效果。10.png

像useMemo和useCallback这样的钩子也以同样的方式处理deps:11.png12.png

从前面的讨论中,我们确定了两件事:

  • 像useEffect这样的钩子(Hooks)访问的是memriorizedState中的数据。
  • 钩子(Hooks)通过比较dep是否相等来决定是否执行回调函数。


闭包陷阱

现在回到闭包陷阱中来:

useEffect(() => { 
    const timer = setInterval(() => { 
        setCount(count + 1); 
    }, 500); 
}, []);
useEffect(() => { 
    const timer = setInterval(() => { 
        console.log(count); 
    }, 500); 
}, []);

deps是一个空数组,所以这个回调只会执行一次。

对应的源码实现如下:

13.png

需要执行的Effect会被标记为HasEffect,之后再执行:14.png

因为deps这里是一个空数组,所以没有HasEffect标志。Effect将不再执行。

所以定时器setInterval只会设置一次。因此,其回调函数引用的状态始终是初始状态,无法获取最新状态。15.png

如果我们想要获得最新的State,那么我们需要让fn在每次重新渲染时执行一次。也就是说,我们应该把count放在依赖数组中。

useEffect(() => {
        setInterval(() => {
            setCount(count + 1);
        }, 1500);
    }, [count]);
useEffect(() => {
        setInterval(() => {
            console.log(count);
        }, 1500);
    }, [count]);

为什么控制台的结果如此混乱?

这是因为每个Effect都创建了一个计数器。所以我们需要清除效果中之前的计数器。

useEffect(() => {
    let timer = setInterval(() => {
      setCount(count + 1);
    }, 1500);
    return() => clearInterval(timer);
  }, [count]);
useEffect(() => {
    let timer = setInterval(() => {
      console.log(count);
    }, 1500);
    return() => clearInterval(timer);
  }, [count]);



小结

  • 一个名为memorizedState的链表存储在Fiber节点中。链表的节点与钩子一一对应,每个钩子访问对应节点上的数据。
  • 像useEffect、useMomo和useCallback这样的钩子都有一个deps参数。每次执行渲染时,都会比较新的和旧的deps,如果deps发生变化,则会重新执行回调函数。
  • 如果 useEffect 第二个参数传入 undefined 或者 null或者没有第二个参数,每次都会执行 ----->不断调用这个回调--->产生无限循环 ;
  • 如果传入了一个空数组,只会执行一次(一般在异步请求的时候这么设置);
  • 第二项为一个非空数组(正常情况),会对比数组中的每个元素有没有改变,来决定是否执行。

产生闭包陷阱的原因是,在useEffect等钩子中使用了某个状态,但它没有添加到deps数组中,因此即使状态发生了变化,也不会重新执行回调函数,它仍然引用旧的状态。(场景总结: 在useEffect中使用到state的时候需要特别小心,尤其是依赖)

闭包陷阱也很容易修复,我们只需要正确设置deps数组。这样,每次状态改变时,回调函数都会被重新执行,引用新的状态。但是,我们还需要注意清理以前的计时器、事件监听器等。


目录
相关文章
|
1月前
|
存储 前端开发 JavaScript
React闭包陷阱产生的原因是什么,如何解决
react闭包陷阱产生的原因是由于在React组件中使用了异步操作(如定时器、事件监听等)时,闭包会保留对旧状态的引用,导致更新后的状态无法正确地被获取或使用。
83 0
|
1月前
|
前端开发
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
|
1月前
|
存储 前端开发 安全
深入理解React中的useState:函数组件状态管理的利器
深入理解React中的useState:函数组件状态管理的利器
|
1月前
|
存储 前端开发 JavaScript
React Hooks实战:从useState到useContext深度解析
React Hooks 深度解析:useState用于函数组件的状态管理,通过初始化和更新状态实现渲染控制;useContext则提供跨组件数据传递。useState的状态更新是异步的,不支持浅比较,可结合useEffect处理副作用。useContext在多层组件间共享状态,但可能导致不必要的渲染。两者结合可创建复杂应用场景,如带主题切换的计数器。了解其工作原理和优化策略,能有效提升React应用性能。
37 0
|
1月前
|
前端开发 JavaScript CDN
React 在 html 中 CDN 引入(包含 useState、antd、axios ....)
React 在 html 中 CDN 引入(包含 useState、antd、axios ....)
144 0
|
1月前
|
前端开发 JavaScript
React中的状态管理:useState与useReducer的使用与探讨
【4月更文挑战第25天】本文探讨了React中构建动态界面的关键——状态管理,重点关注`useState`和`useReducer` Hook。`useState`适用于简单状态管理,例如计数器,而`useReducer`在处理复杂逻辑和多个状态更新时更具优势,提供更好的组织和可维护性。选择使用哪个取决于状态逻辑复杂度、可维护性和性能需求。合理运用这两个工具能实现高效、可维护的React应用。
|
1月前
|
前端开发 数据处理 开发者
React的useState:开启组件状态管理的新篇章
React的useState:开启组件状态管理的新篇章
|
1月前
|
前端开发 JavaScript 测试技术
React Hooks之useState、useRef
React Hooks之useState、useRef
|
1月前
|
存储 前端开发 JavaScript
React Hooks的useState、useRef使用
React Hooks的useState、useRef使用
35 2
|
1月前
|
前端开发 JavaScript
React 钩子:useState()
React 钩子:useState()
49 0
React 钩子:useState()

热门文章

最新文章