React的useEvent 和 ahooks 的 useMemorizedFn 的深度分析和对比

news2024/11/16 18:06:46

父组件

const TestParent: React.FC<any> = () => {
  const [State, setState] = useState(0);

  const changeFun = useCallback(() => {
    console.log('useCallback closure 里的  State', State);
  }, [State]);

  const changeFun_useEvent = useEvent(() => {
    console.log('useEvent closure 里的  State', State);
   });
   
  const changeFun_usememrized = useMemorizedFn(() => {
    console.log('useMemorizedFn closure 里的  State', State);
  });
  return (
    <div style={{ width: '100%', padding: '20px', backgroundColor: 'green' }}>
      父组件的state: {State}

      <button onClick={() => setState(Math.random())}>
        click-change state
      </button>

      <TestChild changeFun={changeFun_useEvent}></TestChild>
      <TestChild changeFun={changeFun_usememrized}></TestChild>

    </div>
  );
};

子组件

const TestChild = (props: any) => {
  const { changeFun } = props;

  useEffect(() => {
    console.log('changeFun-》地址change了,导致了子组件刷新了。');
  }, [changeFun]);

  return (
    <div style={{ backgroundColor: 'pink' }}>
      子组件
      <br />
      <br />
      <button onClick={changeFun}>child click - get parent's state </button>
    </div>
  );
};
React.memo(TestChild);

useEvent

已被官方废弃

useEvent 源码解析

【解释1】
1、由于每次父组件更新,都会执行到 下面的代码。这个是首要条件!
const changeFun_useEvent = useEvent(() => {
console.log('useEvent closure 里的  State', State);
});

2、其次,每次执行都会传递过来一个新的handler,自然带来了新的闭包、新的数据。

3、最后,useLayoutEffect的作用是每次数据发生更新,自动的去执行里面的方法。

4、 所以 handlerRef.current 就能拿到带着最新数据的一个handler

【解释2】

useCallback 并且依赖为[],只执行一次,地址也就永远不会变了。
那么,useEvent()return出去函数的地址永不变化。

可以解释为

1、useEvent() return 出去了一个对象,为  obj = {A:{}}

2、 useCallback里的箭头函数return出去的是这个 fn=handlerRef.current;相当于一个属性 A 
    由于handlerRef.current指向地址是不停变化的,所以A得指向地址就是不停变化的。

3、因此我们真正使用 changeFun_useEvent=useEvent(xx)的时候, 拿到的是obj。永远不变的地址。
  changeFun_useEvent()调用的时候拿到的就是obj.A了。就是最新的带着最新数据的一个handler
function useEvent(handler: any) {
  const handlerRef = useRef(null);

  // In a real implementation, this would run before layout effects
  // 这行保证handlerRef一直处于,最新的闭包、拿到最新的数据。【解释1】
  useLayoutEffect(() => {
    console.log('useLayoutEffect执行了');
    handlerRef.current = handler;
  });
//这行保证返回的数据地址永远不变,但是执行的时候拿到的最新的。【解释2】
  return useCallback((...args: any) => {
    // In a real implementation, this would throw if called during render
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}
useMemorizedFn源码解析

源码地址
【解释1】
fn在这里做依赖的原因: 由于每次父组件重新执行,都会走到useMemorizedFn里并传过来一个新的箭头函数。所以fn每次地址都是新的。也就带来了新数据的闭包。fnRef.current也就是解释2里的A的地址每次都是新的,带着新的数据的闭包。

【解释2】
这里可以解释为: const obj = {A:{}} ;
memoizedFn.current = obj;
fnRef.current = A
当执行memoizedFn.current的时候也就是去访问并执行了 obj.A

function useMemorizedFn(fn: any) {
  const fnRef = useRef(fn);

  // 这行保证fn一直处于,最新的闭包、拿到最新的数据。【解释1】
  //这里为什么不直接  fnRef.current =  fn  ,可参考官网。总结就是 fnRef.current的需要放到useMemo。否则reactdev tool监听不到变化。
  fnRef.current = useMemo(() => {
    console.log('useMemo执行了'); // 父级改变就执行
    return fn;
  }, [fn]);

  const memoizedFn = useRef<any>();
   // 下面保证fn引用地址不变、且每次都能拿到最新的闭包 【解释2】
  if (!memoizedFn.current) {
    memoizedFn.current = function (this: any, ...args: any) {
      return fnRef.current.apply(this, args);//立即执行、改变this指向、传递参数 3个作用
    };
  }

  return memoizedFn.current;
}
总结

由此我们看出。其实二者的原理和出发点都是一致的,都是返回的是个固定的对象obj,该对象地址不变,但是调用的方法的时候相当于调用了obj.A,此属性的指向是会一直更新的。
只不过更新的时候 :useevent用了useLayoutCallback做更新,useMemorizedFn则使用了useMemo。
保持地址不变的时候:useevent用的是useCallback ,useMemorizedFn使用的一个 !memoizedFn.current + 新固定的function 。

此文纯属个人理解,有偏差望指正。

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

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

相关文章

Windows下多Chrome谷歌浏览器版本共存

场景 某些年代久远的 WEB 应用&#xff0c;必须在指定的浏览器或版本才能正常运行&#x1f602;&#xff0c;此时就需要多个版本 chrome 浏览器共存。 解决方案 下载指定版本 可以从 https://www.chromedownloads.net/ 下载需要的版本&#xff0c;此处下载的是87.0.4280.14…

【Python微信机器人】第三篇:使用ctypes调用进程函数和读取内存结构体

目录修整 目前的系列目录(后面会根据实际情况变动): 在windows11上编译python将python注入到其他进程并运行注入Python并使用ctypes主动调用进程内的函数和读取内存结构体使用汇编引擎调用进程内的任意函数利用beaengine反汇编引擎的c接口写一个pyd库&#xff0c;用于实现inl…

Java使用OkHttp库采集电商视频简单代码示例

很多朋友经常问我&#xff0c;能不能用OkHttp库的Java编写一个淘宝视频的采集程序&#xff0c;今天它来了&#xff01;在市面上众多的采集框架中&#xff0c;OkHttp库的应用比较广泛&#xff0c;而且也是非常的稳定&#xff0c;下面的代码示例不知道能不能满足大家的胃口呢&…

Docker安装部署[8.x]版本Elasticsearch+Kibana+IK分词器

文章目录 Docker安装部署elasticsearch拉取镜像创建数据卷创建网络elasticsearch容器&#xff0c;启动&#xff01;踩坑&#xff1a;虚拟机磁盘扩容 Docker安装部署Kibana拉取镜像Kibana容器&#xff0c;启动&#xff01; 安装IK分词器安装方式一&#xff1a;直接从github上下载…

【网络】应用层 -- http协议

目录 一、认识URLurlencode和urldecode 二、HTTP协议HTTP协议格式HTTP的方法HTTP的状态码HTTP常见Header 一、认识URL URL叫做统一资源定位符&#xff0c;也就是我们平时俗称的网址&#xff0c;是因特网的万维网服务程序上用于指定信息位置的表示方法。 urlencode和urldecode …

单元测试反射注解

单元测试 就是针对最小的功能单元(方法)&#xff0c;编写测试代码对其进行正确性测试。 咱们之前是如何进行单元测试的&#xff1f;有啥问题 &#xff1f; Junit单元测试框架 可以用来对方法进行测试&#xff0c;它是由Junit公司开源出来的 具体步骤 Junit框架的常见注解…

resource manager attributes structure(iofunc_attr_t) 扩展实例

文章目录 前言一、attributes structure(iofunc_attr_t)是什么二、iofunc_attr_t 扩展实例1. iofunc_attr_t 未扩展前的使用实例2. iofunc_attr_t 扩展后的使用实例总结参考资料前言 本文主要介绍如何扩展 QNX resource manager 的 attributes structure(iofunc_attr_t) 属性数…

cortex-A7核UART总线

1.总线 各个部件之间传输一种媒介 2.串行总线 3.并行总线 4.同步和异步 同步&#xff1a; 异步&#xff1a; 5.ST-LINK仿真器连接方式 6.串口通信信息---异步串行全双工总线 串口配置信息8N1代表什么? 7.串口通讯协议

三、 链表

一、链表的定义 链表是一种动态数据结果&#xff0c;内存分配不是在创建链表时一次性完成的&#xff0c;每添加一个节点&#xff0c;分配一次内存&#xff0c;由于没有闲置的内存&#xff0c;链表的空间效率高于数组 二、定义单向链表 struct ListNode {int m_nValue;ListNo…

UG NX机械设计软件常见安装问题

UG软件版本这里咱们就不提了&#xff0c;大部分伙伴应该都是钩子激活软件&#xff0c;肯定会遇到或多或少的安装问题&#xff0c;今天这里给大家总结了下&#xff0c;需要的小伙伴自取。 有其他问题可以一起讨论&#xff0c;也希望看到的小伙伴多关注支持哦。 安装UGNX的必要…

第二个GDAL程序

之前看过一个GDAL入门程序&#xff0c;下面再看一个&#xff0c;熟悉一下GDAL&#xff1b; #include "stdafx.h" #include <gdal_priv.h>int main() {//注册文件格式GDALAllRegister();const char* pszFile "D:\\3dtile\\GDALtestdata\\7-Data\\calss.t…

IDEA使用-通过Database面板访问数据库

文章目录 前言操作过程注意事项1.无法下载驱动2.“Database”面板不显示数据库表总结前言 作为一款强大IDE工具,IDEA具有很多功能,本文将以MariaDB数据库访问为例,详细介绍如何通过IDE工具的Database面板来访问数据库。 操作过程 不同的版本操作会略有差异,这里我们用于演…

【Java笔试强训】Day8(WY20 两种排序方法、HJ108 求最小公倍数)

WY20 两种排序方法 链接&#xff1a;两种排序方法 题目&#xff1a; 考拉有n个字符串字符串&#xff0c;任意两个字符串长度都是不同的。考拉最近学习到有两种字符串的排序方法&#xff1a; 1.根据字符串的字典序排序。例如&#xff1a; “car” < “carriage” < “c…

Kubernetes 二进制部署

目录 服务器配置 部署 etcd 集群 准备签发证书环境 部署 docker引擎 flannel网络配置 K8S中Pod网络通信 Overlay Network VXLAN Flannel简介 Flannel工作原理 ETCD之Flannel提供说明 Flannel部署 在master01节点上操作 在所有node节点上操作(以node01为例) 修改d…

【Nginx37】Nginx学习:SSL模块(一)简单配置与指令介绍

Nginx学习&#xff1a;SSL模块&#xff08;一&#xff09;简单配置与指令介绍 又是一个重点模块&#xff0c;SSL 模块&#xff0c;其实就是我们常见的 HTTPS 所需要的配置模块。HTTPS 的重要性不用多说了吧&#xff0c;现在所有的 App、小程序 都强制要求是 HTTPS 的&#xff0…

【数据结构初阶】之单链表

【数据结构初阶】之链表 1. 链表是什么2. 单链表的逻辑结构和物理结构 3.如何创建一个单链表的自定义类型4.单链表的增删查改及各种功能的实现4.1 单链表创建一个节点4.2 单链表的头插4.2.1 头插的函数设计&#xff08;参数类型及其返回值&#xff09;4.2.1 头插的函数实现 4.3…

【SOC基础】单片机学习案例汇总 Part1:电机驱动、点亮LED

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

mybatisplus 增删改查

文章目录 1.BaseMapperBaseMapper中提供的CRUD方法&#xff1a; CURD实例通用ServiceIService中的CRUD方法 Service层操作数据实例 1.BaseMapper 说明: 通用 CRUD 封装BaseMapper 接口&#xff0c;为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器…

集成测试、单元测试、系统测试之间的关系和区别

前言 为了使软件正常工作&#xff0c;所有单元都应集成在一起并正常运行。集成测试就像是要求不同工种的工人修建一个房子&#xff0c;希望他们都团结协作。如何判断他们在一起是否可以按照计划完成建设呢&#xff1f;唯一了解的方法是通过将它们全部拉在一起并测试它们如何相互…

写时拷贝+进程终止

目录 一、写时拷贝 二、创建多进程 三、进程终止 &#xff08;一&#xff09;main函数的返回值 1. main函数的返回值 2. 退出码 3. 查看进程的退出码 &#xff08;二&#xff09;错误码VS退出码 &#xff08;三&#xff09;代码异常终止 1. 基本概念 2. 信号 &…