广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >ahooks解决React闭包问题方法示例
  • 676
分享到

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

2024-04-02 19:04:59 676人浏览 薄情痞子
摘要

引言 本文是深入浅出 ahooks 源码系列文章的第三篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解。学习如何抽象自定义 hooks。构建属于自己的 Re

引言

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

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

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

系列文章:

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

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

React 的闭包问题

先来看一个例子:

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 创建了一个对象。

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 会判断其依赖是否改变,如果改变了就会执行回调函数。

回到刚刚那个例子:

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 设置依赖项,重新执行函数,设置新的定时器,拿到最新值。

// 解决方法一
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 属性的时候,对象的引用都是同一个,所以定时器中能够读到最新的值。

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 包一层:

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 的。

const [count, setCount] = useState(0);
const callbackFn = useCallback(() => {
  console.log(`Current count is ${count}`);
}, []);

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

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

const callbackFn = useEvent(() => {
  console.log(`Current count is ${count}`);
});

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

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

const memoizedFn = useMemoizedFn(() => {
  console.log(`Current count is ${count}`);
});

Demo 地址

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

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闭包的资料请关注编程网其它相关文章!

--结束END--

本文标题: ahooks解决React闭包问题方法示例

本文链接: https://www.lsjlt.com/news/154094.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • ahooks解决React闭包问题方法示例
    引言 本文是深入浅出 ahooks 源码系列文章的第三篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解。学习如何抽象自定义 hooks。构建属于自己的 Re...
    99+
    2022-11-13
  • ahooks解决用户多次提交方法示例
    目录引言场景useLockFn缺点axios 自动取消重复请求axios 取消请求如何自动取消重复的请求思考与总结引言 本文是深入浅出 ahooks 源码系列文章的第四篇,这个系列的...
    99+
    2022-11-13
  • 解决TCP粘包/拆包问题的方法及示例
    TCP粘包和拆包是网络编程中常见的问题,特别是在数据传输的过程中,可能会发生将多个数据包粘在一起或将一个数据包拆成多个数据包的情况,这可能会导致应用程序无法正确解析数据,从而造成数据错误或系统故障。本文将介绍TCP粘包和拆包的原因、解决方案...
    99+
    2023-09-25
    网络 服务器 tcp/ip c# 网络协议
  • Python基于贪心算法解决背包问题示例
    本文实例讲述了Python基于贪心算法解决背包问题。分享给大家供大家参考,具体如下: 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出...
    99+
    2022-06-04
    示例 贪心 算法
  • React样式冲突解决问题的方法
    目录前言:CSS IN JS一、概念二、CSS Modules三、在项目中使用css Modules四、css module配合sass五、module.scss 使用步骤:六、总结...
    99+
    2023-03-10
    React样式冲突 React样式
  • React-vscode使用jsx语法的问题及解决方法
    问题描述 安装了插件ES7 React/Redux/GraphQL/React-Native snippets还是不能完全支持 所以我参考了博客,得出了我的解法 解决方法 打开设置...
    99+
    2022-11-12
  • word遇到问题需要关闭的解决方法
    这篇文章主要介绍word遇到问题需要关闭的解决方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!  第一步,打开word(不是WORD文件而是桌面上或者开始-程序里的word快捷方式) 出现了一个提示,显示...
    99+
    2023-06-06
  • Socket粘包问题的解决方法有哪些
    这篇文章主要讲解了“Socket粘包问题的解决方法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Socket粘包问题的解决方法有哪些”吧!什么是 TC...
    99+
    2022-10-19
  • SpringBoot跨域问题的解决方法实例
    谈到跨域问题,首先我们要认识一下浏览器的同源策略 百度百科对同源策略的解释 当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面 当浏览器的百度tab页执行一个脚本的时候会检查...
    99+
    2022-11-12
  • React报错之Object is possibly null的问题及解决方法
    目录类型守卫非空断言总结类型守卫 使用类型守卫来解决React中useRef钩子“Object is possibly null”的错误。比如说,if (in...
    99+
    2022-11-13
  • MySQL 8.0.31 登录提示caching_sha2_password问题解决方法
    MySQL 8.0.31 登录提示caching_sha2_password问题解决方法 MySQL 8.0.31 使用了 caching_sha2_password 作为默认的身份验证插件,这可能导...
    99+
    2023-10-10
    mysql android 数据库
  • word显示出现问题并且无法关闭如何解决
    要解决Word显示出现问题并且无法关闭的问题,可以尝试以下几个解决办法:1. 强制关闭程序:按住Ctrl + Alt + Delet...
    99+
    2023-09-11
    word
  • wordpress主题中无法显示浏览量问题的解决方法
    然后你犯晕了吧。主要原因是某些主题没有添加WP-postviews这款插件的支持代码,恰巧笔者的Green Park 2这款主题要解决这个问题,那我就来献丑一下吧。 第一步:用FTP或Dreamweaver编辑主题目录下...
    99+
    2022-06-12
    浏览量
  • 亚马逊服务器丢包问题的解决方法
    1. 了解丢包问题 在解决亚马逊服务器丢包问题之前,首先需要了解什么是丢包问题。丢包是指在网络传输过程中,数据包在传输过程中丢失或未能到达目的地的情况。丢包问题可能导致网络连接不稳定,影响用户体验和数据传输的完整性。 2. 检查网络连接 ...
    99+
    2023-10-27
    亚马逊 解决方法 服务器
  • Node.js中下包速度慢问题解决方法分析
    切换npm下包镜像源 查看当前下包镜像源 npm config get registry 将下包镜像源切换为淘宝镜像源 npm config set registry=https:...
    99+
    2023-05-15
    Node.js 下包 速度慢 解决方法 npm nrm
  • JavaGUI插入图片不显示问题解决方法
    问题描述: 在学习使用Java的GUI时,插入图片但是不显示代码如下所示: public abstract class AbstractMainFrame extends JFram...
    99+
    2022-11-12
  • HTML显示中文乱码问题的解决方法
    在使用HTML编写网页或者文章时,很多人都会遇到显示中文乱码的问题。这种问题主要是由于编码格式不对或者字库缺失导致的。如果没有解决这个问题,就会导致网页在不同的浏览器上显示的效果不同,影响网站的美观度和用户的体验。这篇文章将介绍HTML显示...
    99+
    2023-05-14
  • Django框架ORM操作数据库不生效问题示例解决方法
    目录问题描述定位过程 首先,我们怀疑是SQL语句拼装错误(比如ID不对),导致了删除不生效解决方案总结本文详细描述使用Django 的ORM框架操作PostgreSQL数据库删除不生...
    99+
    2023-01-07
    django orm操作数据库不生效 django orm框架操作
  • Java基于递归解决全排列问题算法示例
    本文实例讲述了Java基于递归解决全排列问题算法。分享给大家供大家参考,具体如下:排列问题设R={r1,r2,...,rn}是要进行排列的n个元素,Ri=R-{ri}。集合x中元素的全排列记为Perm(X)。(ri)Perm(X)表示在全排...
    99+
    2023-05-30
    java 递归 全排列
  • SpringBoot项目打包war包时无法运行问题的解决方式
    目录序SpringBoot项目打包jar 包 和 tomcat 包实际问题及解决方式问题解决方法end序 现实的情况下遇见过该问题刚好记录一下! SpringBoot项目打包 首先默...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作