ahooks解决React闭包问题方法示例

news2024/11/17 10:03:17

这篇文章主要为大家介绍了ahooks解决React闭包问题方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

本文是深入浅出 ahooks 源码系列文章的第三篇,这个系列的目标主要有以下几点:

  • 加深对 React hooks 的理解。
  • 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
  • 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。

注:本系列对 ahooks 的源码解析是基于 v3.3.13。自己 folk 了一份源码,主要是对源码做了一些解读,可见 详情。

系列文章:

  • 大家都能看得懂的源码 ahooks 整体架构篇
  • 如何使用插件化机制优雅的封装你的请求hook

本文来探索一下 ahooks 是怎么解决 React 的闭包问题的?。

React 的闭包问题

先来看一个例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

import React, { useState, useEffect } from "react";

export default () => {

  const [count, setCount] = useState(0);

  useEffect(() => {

    setInterval(() => {

      console.log("setInterval:", count);

    }, 1000);

  }, []);

  return (

    <div>

      count: {count}

      <br />

      <button onClick={() => setCount((val) => val + 1)}>增加 1</button>

    </div>

  );

};

代码示例

当我点击按钮的时候,发现 setInterval 中打印出来的值并没有发生变化,始终都是 0。这就是 React 的闭包问题。

产生的原因

为了维护 Function Component 的 state,React 用链表的方式来存储 Function Component 里面的 hooks,并为每一个 hooks 创建了一个对象。

1

2

3

4

5

6

7

type Hook = {

  memoizedState: any,

  baseState: any,

  baseUpdate: Update<any, any> | null,

  queue: UpdateQueue<any, any> | null,

  next: Hook | null,

};

这个对象的 memoizedState 属性就是用来存储组件上一次更新后的 statenext 指向下一个 hook 对象。在组件更新的过程中,hooks 函数执行的顺序是不变的,就可以根据这个链表拿到当前 hooks 对应的 Hook 对象,函数式组件就是这样拥有了state的能力

同时制定了一系列的规则,比如不能将 hooks 写入到 if...else... 中。从而保证能够正确拿到相应 hook 的 state。

useEffect 接收了两个参数,一个回调函数和一个数组。数组里面就是 useEffect 的依赖,当为 [] 的时候,回调函数只会在组件第一次渲染的时候执行一次。如果有依赖其他项,react 会判断其依赖是否改变,如果改变了就会执行回调函数。

回到刚刚那个例子:

1

2

3

4

5

6

const [count, setCount] = useState(0);

useEffect(() => {

  setInterval(() => {

    console.log("setInterval:", count);

  }, 1000);

}, []);

它第一次执行的时候,执行 useState,count 为 0。执行 useEffect,执行其回调中的逻辑,启动定时器,每隔 1s 输出 setInterval: 0

当我点击按钮使 count 增加 1 的时候,整个函数式组件重新渲染,这个时候前一个执行的链表已经存在了。useState 将 Hook 对象 上保存的状态置为 1, 那么此时 count 也为 1 了。执行 useEffect,其依赖项为空,不执行回调函数。但是之前的回调函数还是在的,它还是会每隔 1s 执行 console.log("setInterval:", count);,但这里的 count 是之前第一次执行时候的 count 值,因为在定时器的回调函数里面被引用了,形成了闭包一直被保存。

解决的方法

解决方法一:给 useEffect 设置依赖项,重新执行函数,设置新的定时器,拿到最新值。

1

2

3

4

5

6

7

8

9

// 解决方法一

useEffect(() => {

  if (timer.current) {

    clearInterval(timer.current);

  }

  timer.current = setInterval(() => {

    console.log("setInterval:", count);

  }, 1000);

}, [count]);

解决方法二:使用 useRef。 useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。

useRef 创建的是一个普通 Javascript 对象,而且会在每次渲染时返回同一个 ref 对象,当我们变化它的 current 属性的时候,对象的引用都是同一个,所以定时器中能够读到最新的值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

const lastCount = useRef(count);

// 解决方法二

useEffect(() => {

  setInterval(() => {

    console.log("setInterval:", lastCount.current);

  }, 1000);

}, []);

return (

  <div>

    count: {count}

    <br />

    <button

      onClick={() => {

        setCount((val) => val + 1);

        // +1

        lastCount.current += 1;

      }}

    >

      增加 1

    </button>

  </div>

);

useRef => useLatest

终于回到我们 ahooks 主题,基于上述的第二种解决方案,useLatest 这个 hook 随之诞生。它返回当前最新值的 Hook,可以避免闭包问题。实现原理很简单,只有短短的十行代码,就是使用 useRef 包一层:

1

2

3

4

5

6

7

8

import { useRef } from 'react';

// 通过 useRef,保持每次获取到的都是最新的值

function useLatest<T>(value: T) {

  const ref = useRef(value);

  ref.current = value;

  return ref;

}

export default useLatest;

useEvent => useMemoizedFn

React 中另一个场景,是基于 useCallback 的。

1

2

3

4

const [count, setCount] = useState(0);

const callbackFn = useCallback(() => {

  console.log(`Current count is ${count}`);

}, []);

以上不管,我们的 count 的值变化成多少,执行 callbackFn 打印出来的 count 的值始终都是 0。这个是因为回调函数被 useCallback 缓存,形成闭包,从而形成闭包陷阱。

那我们怎么解决这个问题呢?官方提出了 useEvent。它解决的问题:如何同时保持函数引用不变与访问到最新状态。使用它之后,上面的例子就变成了。

1

2

3

const callbackFn = useEvent(() => {

  console.log(`Current count is ${count}`);

});

在这里我们不细看这个特性,实际上,在 ahooks 中已经实现了类似的功能,那就是 useMemoizedFn。

useMemoizedFn 是持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback。使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。以上的问题,通过以下的方式就能轻松解决:

1

2

3

const memoizedFn = useMemoizedFn(() => {

  console.log(`Current count is ${count}`);

});

Demo 地址

我们来看下它的源码,可以看到其还是通过 useRef 保持 function 引用地址不变,并且每次执行都可以拿到最新的 state 值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

function useMemoizedFn<T extends noop>(fn: T) {

  // 通过 useRef 保持其引用地址不变,并且值能够保持值最新

  const fnRef = useRef<T>(fn);

  fnRef.current = useMemo(() => fn, [fn]);

  // 通过 useRef 保持其引用地址不变,并且值能够保持值最新

  const memoizedFn = useRef<PickFunction<T>>();

  if (!memoizedFn.current) {

    // 返回的持久化函数,调用该函数的时候,调用原始的函数

    memoizedFn.current = function (this, ...args) {

      return fnRef.current.apply(this, args);

    };

  }

  return memoizedFn.current as T;

}

总结与思考

React 自从引入 hooks,虽然解决了 class 组件的一些弊端,比如逻辑复用需要通过高阶组件层层嵌套等。但是也引入了一些问题,比如闭包问题。

这个是 React 的 Function Component State 管理导致的,有时候会让开发者产生疑惑。开发者可以通过添加依赖或者使用 useRef 的方式进行避免。

ahooks 也意识到了这个问题,通过 useLatest 保证获取到最新的值和 useMemoizedFn 持久化 function 的方式,避免类似的闭包陷阱。

值得一提的是 useMemoizedFn 是 ahooks 输出函数的标准,所有的输出函数都使用 useMemoizedFn 包一层。另外输入函数都使用 useRef 做一次记录,以保证在任何地方都能访问到最新的函数。

参考

  • 从react hooks“闭包陷阱”切入,浅谈react hooks
  • React官方团队出手,补齐原生Hook短板

以上就是ahooks解决React闭包问题方法示例的详细内容,更多关于ahooks React闭包的资料请关注脚本之家其它相关文章!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/60883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ShardingSphere介绍

ShardingSphere产品介绍 Apache ShardingSphere 是一款分布式的数据库生态系统&#xff0c; 可以将任意数据库转换为分布式数据库&#xff0c;并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。 设计哲学为 Database Plus&#xff0c;旨在构建异构数据库上层的标准…

项目管理逻辑:为什么职能部门官僚主义气息浓重?

目录 1.管理孤岛 2.业务部门和职能部门之间的关系 3.企业如何转型升级 3.1垂直管理模式 3.2精细管理模式 4.成熟企业的职能部门和组织部门 1.管理孤岛 职能部门办事状态 每个公司通常从管理的角度会划分很多个层级 . 不同层级之间通常会存在管理隔阂,也就是说不同的领…

HCIA笔记(2)

一、地址&#xff08;全球唯一、地址统一&#xff09; 1.MAC地址&#xff1a;芯片出厂时&#xff0c;厂家烧录进去的一个串号。48位二进制构成 2.物理地址&#xff1a;C8-CB-9E-6B-3A-41 --- 为了方便人类区分和识别&#xff0c;我们会将48位二进制转换为12位16进制表示 3.查…

logging java日志选择

文章目录hutoolsslf4japache logging[JDK logging](https://www.liaoxuefeng.com/wiki/1252599548343744/1264738568571776)logbacklog4j2hutools <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>…

一种在 Python 中实现更快 OpenCV 视频流的多线程方法

概述在本文中&#xff0c;我们将看到两个没有多线程的 Python 代码示例&#xff0c;用于从摄像头读取视频帧。我们将看到使用/不使用多线程获得的 FPS 的差异。什么是多线程&#xff1f;线程是进程中的一个执行单元。多线程是指通过在线程之间快速切换对 CPU 的控制&#xff08…

257.二叉树的所有路径

257.二叉树的所有路径 文章目录257.二叉树的所有路径题目题解技巧 - 参数的作用域巧妙的题解方法题目 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&a…

【Redis实战】GeoHash实现原理与基本使用

文章目录GeoHash可以解决什么问题&#xff1f;基于网格划分的最近邻查询GeoHash划分规则GeoHash的使用方式Redis GEOADD 命令语法示例Redis GEORADIUS 命令语法半径单位&#xff1a;可选性参数&#xff1a;示例Redis GEORADIUSBYMEMBER 命令语法示例Redis GEODIST 命令语法示例…

Android 面试中Binder必问,是否了解过?

1.简单介绍下binder binder是一种进程间通讯的机制 进程间通讯需要了解用户空间和内核空间 每个进程拥有自己的独立虚拟机&#xff0c;系统为他们分配的地址空间都是互相隔离的。 如两个进程需要进行通讯&#xff0c;则需要使用到内核空间做载体&#xff0c;内核空间是所有进…

GPS卫星位置解算

本文介绍了基于C语言的GPS卫星位置解算原理与程序设计。针对每个原理、公式、代码设计进行了详细讲解&#xff0c;希望能够给测绘学子们带来帮助。 参考书籍&#xff1a; 李征航 黄劲松&#xff1a;GPS测量与数据处理&#xff08;第三版&#xff09; 目录 基础原理 1&#xf…

Spring框架(十一):手动实现一个@Component,讲一讲Spring的工厂后处理器

手动实现一个Component&#xff0c;讲一讲Spring的工厂后处理器引子需求代码分析Spring工厂后处理器引子 痛定思痛&#xff0c;主要问题出现在自己雀氏不熟悉框架底层、一些面试题&#xff0c;以及sql的一些情况淡忘了。 本章节的开始是对于过去的重新回顾&#xff0c;当然&am…

【AI】Python 实现八数码问题

实验一 八数码问题 1. 题目介绍 八数码问题描述为&#xff1a;在 33 组成的九宫格棋盘上&#xff0c;摆有 8 张牌&#xff0c;每张牌都刻有 1-8 中的某一个数码。棋盘中留有一个空格&#xff0c;允许其周围的某张牌向空格移动&#xff0c;这样通过移动牌就可以不断改变棋盘布…

PDF或PPT中的某个图或表无损、高清的插入word里的方法,再转成pdf后放大6400%倍仍是高清图片...

本人使用Microsoft Office LTSC 专业增强版 2021版本的&#xff0c;其他版本不确定可不可以 可通过office tool plus下载安装相应版本的office&#xff0c;通过安装与激活安装与激活 0. 参考方法网址&#xff1a; PDF 转成 SVG 格式的方法(无损保留笔记痕迹) 1. pdf可能很多页&…

Dubbo SPI扩展机制源码详解(基于2.7.10)

Dubbo SPI 一. 概述 本文主要分享 Dubbo 的拓展机制 SPI。 想要理解 Dubbo &#xff0c;理解 Dubbo SPI 是非常必须的。在 Dubbo 中&#xff0c;提供了大量的拓展点&#xff0c;基于 Dubbo SPI 机制加载 Dubbo SPI官方文档&#xff1a;Dubbo SPI 概述 | Apache Dubbo 本文基…

Spring-IOC控制反转

Spring 1.简介 1.1简介 1.常见的框架&#xff1a; 2.常见的依赖&#xff1a; <dependencies><!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --><dependency><groupId>org.springframework</groupId><artif…

SpringBoot基础之声明式事务和切面事务和编程式事务

文章目录前言一、事务特性开启事务二、事务的隔离级别三、事务的传播行为四、 Springboot事务1.Springboot声明式事务优点&#xff1a;缺点&#xff1a;实现方式&#xff1a;Transactional的参数声明式事务的约定流程&#xff1a;2. Springboot编程式事务SpringBoo切面编程式事…

[附源码]Python计算机毕业设计SSM教学团队管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

微服务框架 SpringCloud微服务架构 11 自定义镜像 11.2 Dockerfile

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构11 自定义镜像11.2 Dockerfile11.2.1 什么是Dcokerfile11.2.2 直接开干11.…

【机器学习】支持向量机【上】硬间隔

有任何的书写错误、排版错误、概念错误等&#xff0c;希望大家包含指正。 在阅读本篇之前建议先学习&#xff1a; 【机器学习】拉格朗日对偶性 【机器学习】核函数 由于字数限制&#xff0c;分成两篇博客。 【机器学习】支持向量机【上】硬间隔 【机器学习】支持向量机【下】…

绿色荧光染料FITC-PEG-FA,Folic acid-PEG-Fluorescein,荧光素-聚乙二醇-叶酸

​ 1、名称 英文&#xff1a;FITC-PEG-FA&#xff0c;Folic acid-PEG-Fluorescein 中文&#xff1a;荧光素-聚乙二醇-叶酸 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a; Fluorescent PEG Folic acid&#xff08;FA&#xff09; PEG 4、分子量&#xff1a;可定制&a…

[附源码]计算机毕业设计网上书城网站Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…