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数组。这样,每次状态改变时,回调函数都会被重新执行,引用新的状态。但是,我们还需要注意清理以前的计时器、事件监听器等。


目录
相关文章
|
6天前
|
前端开发 JavaScript
深入理解并实践React Hooks —— useEffect与useState
深入理解并实践React Hooks —— useEffect与useState
26 1
|
30天前
|
前端开发 JavaScript API
|
1月前
|
前端开发 开发者
介绍React的useState
【8月更文挑战第6天】 介绍React的useState
33 1
|
2月前
|
存储 前端开发 安全
|
2月前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
56 1
|
2月前
|
前端开发
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
38 0
|
2月前
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
29 0
|
2月前
|
前端开发 JavaScript
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
100 0
|
4月前
|
前端开发
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
|
4月前
|
存储 前端开发 安全
深入理解React中的useState:函数组件状态管理的利器
深入理解React中的useState:函数组件状态管理的利器

热门文章

最新文章