当前位置: 首页 > news >正文

react中的useCallback、useMemo、useRef 和 useContext

前言

一、useCallback  缓存回调函数

使用方式 

二、useMemo:缓存计算的结果

三、useRef:在多次渲染之间共享数据

四、useContext:定义全局状态 

总结


前言

上一个文章中我们讲述了useState 和 useEffect 这两个最为核心的 Hooks 的用法。理解了它们,基本上就掌握了 React 函数组件的开发思路。


在 React 函数组件中,每一次 UI 的变化,都是通过重新执行整个函数来完成的,这和传统的 Class 组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。

一、useCallback  缓存回调函数

比如下面的代码中,我们在加号按钮上定义了一个事件处理函数,用来让计数器加 1。但是因为定义是在函数组件内部,因此在多次渲染之间,是无法重用 handleIncrement 这个函数的,而是每次都需要创建一个新的:

使用方式 

function Counter() {const [count, setCount] = useState(0);const handleIncrement = () => setCount(count + 1);// ...return <button onClick={handleIncrement}>+</button>
}
useCallback(fn, deps)

不妨思考下这个过程。每次组件状态发生变化的时候,函数组件实际上都会重新执行一遍。在每次执行的时候,实际上都会创建一个新的事件处理函数 handleIncrement。这个事件处理函数中呢,包含了 count 这个变量的闭包,以确保每次能够得到正确的结果。

这也意味着,即使 count 没有发生变化,但是函数组件因为其它状态发生变化而重新渲染时,这种写法也会每次创建一个新的函数。创建一个新的事件处理函数,虽然不影响结果的正确性,但其实是没必要的。因为这样做不仅增加了系统的开销,更重要的是:每次创建新函数的方式会让接收事件处理函数的组件,需要重新渲染。

比如这个例子中的 button 组件,接收了 handleIncrement ,并作为一个属性。如果每次都是一个新的,那么这个 React 就会认为这个组件的 props 发生了变化,从而必须重新渲染。因此,我们需要做到的是:只有当 count 发生变化时,我们才需要重新定一个回调函数。而这正是 useCallback 这个 Hook 的作用。

修改后

import React, { useState, useCallback } from 'react';function Counter() {const [count, setCount] = useState(0);const handleIncrement = useCallback(() => setCount(count + 1),[count], // 只有当 count 发生变化时,才会重新创建回调函数);// ...return <button onClick={handleIncrement}>+</button>
}

这样,只有 count 发生变化的时候,才需要重新创建一个回调函数,这样就保证了组件不会创建重复的回调函数。而接收这个回调函数作为属性的组件,也不会频繁地需要重新渲染。

二、useMemo:缓存计算的结果

代码如下(示例):

useMemo(fn, deps);

这里的 fn 是产生所需数据的一个计算函数。通常来说,fn 会使用 deps 中声明的一些变量来生成一个结果,用来渲染出最终的 UI。

这个场景应该很容易理解:如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算。 

通过 useMemo 这个 Hook,可以避免在用到的数据没发生变化时进行的重复计算。如果是一个复杂的计算,那么对于提升性能会有很大的帮助。这也是 userMemo 的一大好处:避免重复计算

除了避免重复计算之外,useMemo 还有一个很重要的好处:避免子组件的重复渲染。如果一个子组件的属性的值被缓存了,一旦能够缓存上次的结果,就和 useCallback 的场景一样,可以避免很多不必要的组件刷新。

useCallback 的功能其实是可以用 useMemo 来实现的。

 const myEventHandler = useMemo(() => {// 返回一个函数作为缓存结果return () => {// 在这里进行事件处理}}, [dep1, dep2]);

从本质上来说,它们只是做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新得到。

三、useRef:在多次渲染之间共享数据

函数组件虽然非常直观,简化了思考 UI 实现的逻辑,但是比起 Class 组件,还缺少了一个很重要的能力:在多次渲染之间共享数据。

在类组件中,我们可以定义类的成员变量,以便能在对象上通过成员属性去保存一些数据。但是在函数组件中,是没有这样一个空间去保存数据的。因此,React 让 useRef 这样一个 Hook 来提供这样的功能。

const myRefContainer = useRef(initialValue);

使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方。

除了存储跨渲染的数据之外,useRef 还有一个重要的功能,就是保存某个 DOM 节点的引用。

四、useContext:定义全局状态 

React 组件之间的状态传递只有一种方式,那就是通过 props。这就意味着这种传递关系只能在父子组件之间进行。

看到这里你肯定会问,如果要跨层次,或者同层的组件之间要进行数据的共享,那应该如何去实现呢?这其实就涉及到一个新的命题:全局状态管理。为此,React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。

这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。那么在函数组件里,我们就可以使用 useContext 这样一个 Hook 来管理 Context。

const value = useContext(MyContext);

Context 提供了一个方便在多个组件之间共享数据的机制。不过需要注意的是,它的灵活性也是一柄双刃剑。你或许已经发现,Context 相当于提供了一个定义 React 世界中全局变量的机制,而全局变量则意味着两点:

  1. 会让调试变得困难,因为你很难跟踪某个 Context 的变化究竟是如何产生的。
  2. 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上。

所以在 React 的开发中,除了像 Theme、Language 等一目了然的需要全局设置的变量外,我们很少会使用 Context 来做太多数据的共享。需要再三强调的是,Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。


总结

最后来总结一下今天的所学。在这节课,你看到了 4 个常用的 React 内置 Hooks 的用法,包括:useCallback、useMemo、useRef 和 useContext。

事实上,每一个 Hook 都是为了解决函数组件中遇到的特定问题。因为函数组件首先定义了一个简单的模式来创建组件,但与此同时也暴露出了一定的问题。所以这些问题就要通过 Hooks 这样一个统一的机制去解决,可以称得上是一个非常完美的设计了。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • TypeScript 面试题汇总
  • 多系统萎缩不慌张,这些维生素是你的“守护神”✨
  • RabbitMQ(面试篇)
  • qt圆环饼状图,非常小的窗口都能显示
  • 探索AI大模型量化前沿技术:引领智能计算新潮流
  • SD Maid SE v1.2.3 — 老牌清理工具全面升级,更简洁,更流畅了
  • 如何使用 SQL Server 内置函数实现MD5加密
  • WebSocket的应用场景与案例解析
  • 自然语言处理系列三十三》 语义相似度》同义词词林》算法原理
  • 构建高效的串行任务执行器:SerialExecutor深度解析
  • 长视频生成研究的挑战、方法与前景
  • Nginx知识详解(理论+实战更易懂)
  • 浏览器非安全端口号
  • 单片机驱动彩屏最简方案:单片机_RA8889最小开发板驱动控制TFT彩屏介绍(二)硬件电路设计
  • 咸鱼之王手游内购修复无bug运营版联网架设+后台
  • Github访问慢解决办法
  • Java小白进阶笔记(3)-初级面向对象
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Spring声明式事务管理之一:五大属性分析
  • STAR法则
  • vue-cli在webpack的配置文件探究
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 从零开始学习部署
  • 第2章 网络文档
  • 开源地图数据可视化库——mapnik
  • 利用DataURL技术在网页上显示图片
  • 聊聊hikari连接池的leakDetectionThreshold
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 人脸识别最新开发经验demo
  • -- 数据结构 顺序表 --Java
  • 跳前端坑前,先看看这个!!
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • HanLP分词命名实体提取详解
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 阿里云ACE认证学习知识点梳理
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • # windows 安装 mysql 显示 no packages found 解决方法
  • #100天计划# 2013年9月29日
  • #vue3 实现前端下载excel文件模板功能
  • #window11设置系统变量#
  • (07)Hive——窗口函数详解
  • (160)时序收敛--->(10)时序收敛十
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (亲测有效)推荐2024最新的免费漫画软件app,无广告,聚合全网资源!
  • (三)Kafka 监控之 Streams 监控(Streams Monitoring)和其他
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (十六)视图变换 正交投影 透视投影
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .gitattributes 文件