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

UseMemo、UseCallback、React.memo

  前置问题:React中的函数式组件什么时候会重新渲染?
  答:组件中的状态改变(包含Context)、父组件重新渲染导致子组件的重新渲染。

  组件多次重新渲染会导致性能的下降。UseMemo、UseCallback、React.memo都利用缓存机制来提高了组件的性能。
  UseMemo类似于Vue中的计算属性,将组件中计算出的值进行缓存;
  UseCallback将组件中的函数进行缓存;
  React.memo通过监听props的值是否改变来控制子组件是否重新渲染;

UseMemo

官方解释
代码逻辑解释:
  使用useState钩子创建了一个userInfo状态,包含name和gender属性。
  函数formatGender 用来根据用户的gender属性来将其格式化为中文。
  handleClick 方法用来改变userInfo中的name属性。
  gender 是根据函数formatGender 生成的一个值。

不使用UseMemo

  当点击两个按钮,不管是修改name还是修改gender,组件中的状态都发生改变了,所以组件要重新渲染。组件的重新渲染导致formatGender 函数也要重新执行,可以看到函数中的打印语句。
  如果是修改gender导致组件的重新渲染,那么formatGender 函数重新执行,这无可厚非。
  但是如果是修改name导致组件的重新渲染,那么formatGender 函数就不应该重新执行,因为就算它执行了,计算出来的结果与组件重新渲染之前的计算结果还是相同的,这就白白浪费了性能。如果这个计算过程很昂贵,那么性能浪费的更多。

使用UseMemo

  上述的情况可以使用UseMemo这个钩子进行优化,缓存函数上一次的计算结果。只有当特定的状态发生变化时,才会重新进行重新计算,提高了性能。

import { useState, useMemo } from "react";
export const EgOfUseMemo = () => {const [userInfo, setUserInfo] = useState({name: "nobody",gender: "male",});const formatGender = (gender) => {console.log("调用了翻译性别的方法");return gender === "male" ? "男" : "女";};const handleClick = () => {setUserInfo({...userInfo,name: "lvxiaobu",});};const updateSex = () => {setUserInfo({...userInfo,gender: userInfo.gender + 'aaa',});};// 优化前,不使用useMemo的情况下,修改其他属性,也会重新调用formatGender方法,浪费计算资源// 每次点击按钮,都会调用一次formatGender方法// const gender = formatGender(userInfo.gender);// 优化后,修改其他属性,不会重新调用formatGender方法,性能提升// 每次点击按钮,都不会调用formatGender方法const gender = useMemo(() => {return formatGender(userInfo.gender);}, [userInfo.gender]);return (<div>姓名: {userInfo.name} -- 性别: {gender} <br /><button onClick={handleClick}>修改名字</button><button onClick={updateSex}>修改性别</button></div>);
};

UseCallback

官方解释

不使用UseCallback

  为方便调试,请在入口文件中关闭React中的严格模式
代码解释:
  使用useState钩子生成两个状态count和name,并定义两个更新状态的函数updateCount 和updateName 。
  为了监听Fn函数是否是新生成的,使用useRef钩子缓存Fn函数的上一次引用;使用useEffect钩子监听Fn函数的变化,并比较Fn函数两次的引用地址是否相同。
  当组件挂载时,useEffect钩子将Fn函数的引用缓存了起来。
  当通过按钮来改变count或name时,组件中的状态发生改变,进行重新渲染。触发useEffect钩子,打印出预料中的结果(Fn的新引用和旧引用不相等,因为重新渲染的时候生成的是一个新函数,虽然新函数和旧函数的逻辑是一样的,但是在内存中的地址不一样)。

import React from "react";
import { useCallback, useState, useEffect, useRef } from "react";
import Mock from "mockjs";const EgOfUseCallback = () => {const [count, setCount] = useState(0);const [name, setName] = useState("zhangsan");const previousCacheFnRef = useRef(null);const updateCount = () => {setCount(count + 1);};const updateName = () => {setName(Mock.mock("@name"));};const Fn = () => {console.log("count变化了 ", count);};useEffect(() => {if (previousCacheFnRef.current !== null) {console.log("Fn 引用发生变化");console.log("新引用和旧引用是否相等:",previousCacheFnRef.current === cacheFn);}previousCacheFnRef.current = cacheFn;}, [Fn]);return (<div><p>count : {count}</p><button onClick={updateCount}>updateCount</button><p>name : {name}</p><button onClick={updateName}>updateName</button><br /><button onClick={cacheFn}>cacheFn</button></div>);
});
export default EgOfUseCallback;
使用UseCallback

 &emsp使用了useCallback之后,只有组件中特定的状态发生变化时,组件才会在重新渲染的时候生成一个新函数,否则就返回缓存的函数。可以通过useEffect钩子中的打印语句进行验证。

  ......//仅仅需要将函数体用useCallback钩子进行包裹const cacheFn = useCallback(() => {console.log("count变化了 ", count);}, [count]);......

React.memo

官方描述
  一般来说,当父组件中的状态变更之后,父组件中引用的子组件也要进行刷新。
  但是如果子组件中并没有用到父组件中变更的这个状态,那么理论上子组件是不需要进行刷新的。

不使用memo

代码解释:
  在父组件中定义了两个状态,count和name。仅仅传递count的状态给子组件。
  但是无论在父组件中改变哪个状态,都会触发子组件的重新渲染,可以通过子组件中useEffect钩子中的打印语句加以验证。

//App.jsx
import { useState } from "react";
import Mock from "mockjs";
function App() {const [count, setCount] = useState(0);const [name, setName] = useState(Mock.mock("@name"));return (<><span>APP中的name:{name}</span><buttononClick={() => {setName(Mock.mock("@name"));}}>改变name</button><button onClick={() => setCount(count + 1)}>count+1</button><Child count={count}></Child></>);
}
export default App;
//Child.jsx
const Child = ({ count }) => {useEffect(() => {console.log("组件重新渲染了");});console.log("refesh");return (<div><p>App传递过来的count:{count}</p></div>);
};
export default Child;
使用memo

  如果使用memo,就可以让子组件选择性的进行刷新。只有父组件传递给子组件的状态发生改变了,才会重新渲染子组件。
将上述子组件中的代码用memo进行包裹:

//Child.jsx
const Child = React.memo(({ count }) => {useEffect(() => {console.log("组件重新渲染了");});console.log("refesh");return (<div><p>App传递过来的count:{count}</p></div>);
});
Child.displayName = 'Child'
export default Child;

总结

  上述两个钩子和一个API都是围绕状态来展开的。根据依赖的状态是否变化,选择性的进行值、函数或者组件的刷新。
  虽然通过缓存可以提升性能,但是使用时也会造成额外的代价,比如增加内存用量、额外监听特定的状态是否变化了等等,所以在使用之前要确定自己是否真的需要它。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • RK3568笔记三十六:LED驱动开发(设备树)
  • 香橙派AIpro-携手华为-为AI赋能
  • 全时守护,无死角监测:重点海域渔港视频AI智能监管方案
  • Flink History Server配置
  • 【Python进阶】正则表达式、pymysql模块
  • 《Towards Black-Box Membership Inference Attack for Diffusion Models》论文笔记
  • 【CMU博士论文】结构化推理增强大语言模型(Part 0)
  • 上海理工大学24计算机考研考情分析!初复试分值比55:45,复试逆袭人数不算多!
  • 发布支持TS的npm包
  • 仅两家!云原生向量数据库 PieCloudVector 全项通过信通院「可信数据库」评测
  • 全国媒体邀约,主流媒体到场出席采访报道
  • 20240718每日后端------------kafka VS RabbitMQ:选择正确的消息代理
  • Flutter应用开发:掌握StatefulWidget的实用技巧
  • 字节面试:如何让单机下Netty支持百万长连接?
  • Spring MVC-什么是Spring MVC?
  • 时间复杂度分析经典问题——最大子序列和
  • “大数据应用场景”之隔壁老王(连载四)
  • 【347天】每日项目总结系列085(2018.01.18)
  • Angular 响应式表单 基础例子
  • Angular2开发踩坑系列-生产环境编译
  • Angular数据绑定机制
  • Django 博客开发教程 8 - 博客文章详情页
  • Docker入门(二) - Dockerfile
  • Redis学习笔记 - pipline(流水线、管道)
  • spark本地环境的搭建到运行第一个spark程序
  • WebSocket使用
  • 翻译--Thinking in React
  • 飞驰在Mesos的涡轮引擎上
  • 开发基于以太坊智能合约的DApp
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 蓝海存储开关机注意事项总结
  • 我的面试准备过程--容器(更新中)
  • 移动端唤起键盘时取消position:fixed定位
  • 用jQuery怎么做到前后端分离
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • #java学习笔记(面向对象)----(未完结)
  • (1)无线电失控保护(二)
  • (3)STL算法之搜索
  • (二十四)Flask之flask-session组件
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (四) Graphivz 颜色选择
  • (一)Linux+Windows下安装ffmpeg
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .bat文件调用java类的main方法
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .Net mvc总结
  • .NET 快速重构概要1
  • .net 托管代码与非托管代码
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?