news 2026/4/12 10:20:43

6.React记忆化处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
6.React记忆化处理

使用Hook进行记忆化处理

React中,每次渲染时函数组件都会被调用,这意味着频繁的计算和函数创建可能会对性能产生负面影响。为了优化性能并防止不必要的重新计算,React提供了3种钩子:useMemo()useCallback()useRef()。这些钩子分别允许记忆化值、函数和引用。在项目中使用这三种记忆化处理优化可以提高代码效率。

useMemo() 钩子

useMemo()钩子用于记忆化计算结果,确保只有在依赖项发生变化时才重新计算,用于优化函数组件的性能。它接收一个函数和一个依赖项数组,并返回记忆化值。

以下是使用useMemo()钩子的示例:

import{useMemo}from'react';// 计算第 index 项的斐波那契值functioncomputeExpensiveValue(index){leta=1;letb=1;for(leti=0;i<index-1;++i){b=b+a;a=b-a;}returna;}functionComponent(){constdependency=10;constexpensiveResult=useMemo(()=>{// Expensive computationreturncomputeExpensiveValue(dependency);},[dependency]);return(<div>{expensiveResult}</div>);}

在这个例子中,我们使用useMemo记忆化了expensiveResult值。当依赖值发生变化时,函数内的计算才会执行。如果依赖保持不变,将返回之前记忆化的值,而不是重新计算结果。

React.memo 组件

除了优化组件中的涉及昂贵计算的函数,React也提供了React.memo组件,用于优化函数组件的性能。它通过记忆化组件的渲染输出,在组件的props没有变化时跳过重新渲染,从而提高性能。

React.memo的基本使用方法是将一个函数组件作为参数传递给React.memo,并返回一个记忆化组件。

importReactfrom'react';import{useState}from'react';// 计算第 index 项的斐波那契值constExpensiveComponent=React.memo(({index})=>{leta=1;letb=1;for(leti=0;i<index-1;++i){b=b+a;a=b-a;}console.log("ExpensiveComponent");return<div>{a}</div>;});functionMyComponent({index}){console.log("MyContent");return<div>{index}</div>}functionComponent(){const[count,setCount]=useState(0);const[index,setIndex]=useState(10);return(<div><span>{count}</span><button onClick={function(){setCount(count+1);}}>+</button><MyComponent index={index}/><ExpensiveComponent index={index}/></div>);}

在上面这个例子中,用户通过按钮操作改变了count时,Component组件会因为状态改变重新渲染,但是Component的子组件ExpensiveComponent却因为是memo组件且它的props没有发生变化所以不会被重新渲染。同样作为子组件MyComponent却会被重新渲染。确保你的组件实在<React.StrictMode>模式下渲染的,然后打开命令行点击“+”按钮,就可以观察到只会打印"MyComponent",而不会打印"ExpensiveComponent"。

也许你会看到一次点击,打印了两次,那是因为在React.StrictMode模式下,为了确保不出错,组件会被调用两次。

默认情况下,React.memo只会对比前后的props,如果没有变化则不会重新渲染。当然咱们也可以通过传递一个自定义比较函数来更精确地控制重新渲染的逻辑。

// 计算第 index 项的斐波那契值constExpensiveComponent=React.memo(({index})=>{leta=1;letb=1;for(leti=0;i<index-1;++i){b=b+a;a=b-a;}console.log("ExpensiveComponent");return<div>{a}</div>;},function(prevProps,currentProps){// 当前 props 中的 index 如果为 偶数则不重新渲染returncurrentProps.index%2;});

这个比较函数接收两个props对象,第一个表示上一次的props参数,第二个表示当前的props参数,返回一个布尔值。加上这个自定义比较函数之后,是否重新渲染会依赖这个函数的返回值是否为true,为true怎返回上一次的渲染结果,否则重新渲染并返回新的渲染结果。

React.memo适用于以下场景:

  1. 静态数据展示: 组件接收的props很少变化,但是组件本身比较复杂,重新渲染的成本较高。
  2. 性能优化: 在大列表或表格中,每个项目都是独立的组件,使用React.memo可以避免不必要的重新渲染。
  3. 避免深度相等检查: 在正式运行模式下,组件其实也是根据其props参数是否变化而选择渲染的,但是有些参数可能比较复杂,自定义函数可以避免深度相等检查,特别是在props包含大量数据时。

需要注意的是在默认情况下,也就是没有自定义比较函数传入的情况下,React.memo只进行浅比较(只比较props的一级内容),所以对于嵌套对象咱们需要传入对应的自定义比较函数。同时,React.memo只根据props的变化进行重新渲染,如果这个组件内部定义了状态(state)或者上下文(context),这二者在变化的时候也不会触发组件的重新渲染。

React.memoReact中一个很强大的高阶组件,在合适的使用场景下正确它能够避免不必要的重复渲染有效的提高整个项目的运行效率。

useCallback() 钩子

当函数组件渲染时,它的所有函数都会被重新创建,包括在组件内部定义的任何内联回调。这可能导致接收这些回调作为props的子组件进行不必要的重新渲染,因此它们将回调视为新的引用并触发重新渲染。要想在项目中避免这种函数重新创建,咱们就应该学会使用useCallback钩子。

useCallback()钩子用于记忆化函数。意味着它只会被创建一次,并且在依赖项没有变化的情况下,在随后的渲染中不会被重新创建。useCallback()接收两个参数:第一个是想要记忆化的函数,第二个是依赖项列表,这些依赖项用来确定React何时应该重新创建这个函数,而不是使用记忆化版本。

functionMyComponent(){return<MyButton onClick={()=>console.log("click")}/>};

这个例子中,我们为onClick属性提供的内联函数将在MyComponent每次渲染时被创建。这意味着MyButton组件每次都将接收一个新的函数引用,这将导致MyButton组件进行新的渲染。

以下是一个演示使用useCallback钩子的例子:

importReactfrom'react';constMyComponent=()=>{constclickHandler=React.useCallback(()=>{console.log("click");},[]);return<MyButton onClick={clickHandler}/>};

在这个例子中,clickHandler函数使用useCallback进行了记忆化,空的依赖项数组[]表明该函数没有依赖项,并应在组件的生命周期内保持不变。

添加了useCallback的结果是,在MyComponent的每次渲染中,都向MyButton提供了相同的函数实例,防止了子组件不必要的重新渲染。

useRef() 钩子

当咱们希望咱们的组件“记住”某些信息,但又不想让这些信息触发新的渲染时,咱们就需要使用ref。当然这个好像是有点鸡肋,咱们是否能够通过局部变量完成这个功能呢?好像在组件状态中有这么一段代码。

functionMyContent(){letindex=0;return(<div><span>{index}</span><button onClick={function(){index+=1;console.log(index);}}>+</button></div>);}

在咱们点击“+”按钮时,在命令行中打印的值是一直变化的。但是当涉及到组件嵌套,当咱们的这个组件作为子组件被重新渲染时这个局部变量就会被重新赋值为0。有兴趣的小伙伴可以尝试一下。Refreference的缩写,字面意思就是引用,从事过Java后端开发或者C++开发的小伙伴应该理解这个和变量的区别。引用就相当于独立于函数存在的变量,在函数销毁和重建之后,这个值依然是可以保持不变的。

当然,如果咱们用一个组件外的全局变量也是可以实现这个功能的,但是全局变量过多不容易管理,随着项目的一步步开发,过多的全局变量会让咱们很难读懂和维护代码。所以在遇到需要组件“记住”某些信息的时候,还是推荐使用官方给咱们提供的这个useRef钩子函数。useRef的使用方式如下:

import{useRef}from'react';functionMyContent(){constindex=useRef(0);return(<div><span>{index.current}</span><button onClick={function(){index.current+=1;console.log(index.current);}}>+</button></div>);}

使用useRef会返回一个这样的对象:

{current:0// 你向 useRef 传入的值}

咱们可以用ref.current属性访问该ref的当前值。不同于state的是这个值是有意被设置为可变的,意味着你既可以读取它也可以写入它。

这里的ref指向一个数字,但是,像state一样,咱们可以让它指向任何东西:字符串、对象,甚至是函数。

请注意,组件不会在每次递增时重新渲染。 与state一样,React会在每次重新渲染之间保留ref。但是,设置state会重新渲染组件,更改ref不会!

相比于state咱们使用ref的时候应该清楚,它的改变并不会触发组件的重新渲染,所以在上述代码中虽然ref改变了,但是<span>{index.current}</span>中的显示的内容却不会改变,也就是说对于使用页面的普通人是看不到改变的,这就需要我们知道,尽量不要去在渲染期间读取ref.current(在JSX显示部分),这会导致代码不可靠。

一般Ref的应用场景就是去替代一些咱们需要使用不需要展示的“全局变量”的地方。比如在组件设置了一个timeout,那么就需要这样一个ref去记录它的ID,以便在使用完成之后清理它。

此外,useRef还可以用来访问DOM节点或React组件实例。

ref和DOM

可以将ref指向任何值。但是,ref最常见的用法是访问DOM元素。例如,如果你想以编程方式聚焦一个输入框,这种用法就会派上用场。当咱们将ref传递给JSX中的ref属性时,比如<div ref={myRef}>React会将相应的DOM元素放入myRef.current中。当元素从DOM中删除时,React会将myRef.current更新为null

由于React会自动处理更新DOM以匹配你的渲染输出,因此你在组件中通常不需要操作DOM。但是,有时你可能需要访问由React管理的DOM元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。在React中没有内置的方法来做这些事情,所以你需要一个指向DOM节点的ref来实现。

import{useRef}from'react';functionComponent(){constinputRef=useRef(null);consthandleButtonClick=()=>{inputRef.current.focus();};return(<div><input type="text"ref={inputRef}/><button onClick={handleButtonClick}>Focus Input</button></div>);};

在这个例子中,我们使用useRef创建了inputRef,并将其分配给输入元素的ref属性,这允许使用inputRef.current属性访问DOM节点。在handleButtonClick函数中,需要调用inputRef.current上的focus方法,在单击按钮时聚焦输入元素。

代码层面上,这个过程是这样的:useRef Hook返回一个对象,该对象有一个名为current的属性。最初,inputRef.currentnull。当React为这个<input>创建一个DOM节点时,React会把对该节点的引用放入inputRef.current。然后,咱们可以从事件处理器(handleButtonClick)访问此DOM节点,并使用在其上调用定义的内置浏览器API

通过使用useRef访问DOM节点,用户可以在不触发组件重新渲染的情况下直接与底层DOM元素交互。

访问另一个组件的DOM节点:

ref也是可以像其他props属性参数一个从父组件传递给子组件的。

import{useRef}from'react';functionMyInput({ref}){return<input ref={ref}/>;}functionMyForm(){constinputRef=useRef(null);return<MyInput ref={inputRef}/>}

在上面的代码中,父组件MyForm创建了一个名为inputRefref,并且将它传递给了MyInput子组件。MyInput将这个ref传递给<input>。因为<input>是一个内置组件,在渲染时React会将ref.current属性设置为这个<input> DOM元素。

MyForm中创建的inputRef现在指向MyInput返回的<input> DOM元素。在MyForm中创建的点击处理程序可以访问inputRef并且调用focus()来将焦点设置在<input>上。

React 与 Ref的DOM操作

React中,每次页面更新都分为两个阶段:

  • 渲染阶段,React调用你的组件来确定屏幕上应该显示什么。
  • 提交阶段,React把变更应用于DOM

通常,在渲染期间访问refs是没必要的。这也适用于保存DOM节点的refs。在第一次渲染期间,DOM节点尚未创建,因此ref.current将为null。在渲染更新的过程中,DOM节点还没有更新。所以读取它们还为时过早。

React在提交阶段设置ref.current。在更新DOM之前,React将受影响的ref.current值设置为null。更新DOM后,React立即将它们设置到相应的DOM节点。

通常,咱们都是从事件处理器访问ref的。官方手册上说,Ref是一种给咱们的一种脱围机制,也就是咱们在需要“跳出React”时使用它。什么时候咱们需要“跳出React”呢?比如上文中获取input框的焦点,滚动位置,或者调用其他某些React没有暴漏的浏览器DOM操作API

需要注意的是,如果咱们使用浏览器的某些修改DOM的API,则所做出的修改可能会给React所做出的更改发生冲突。比如咱们使用remove()将这个节点给删除了,而React通过状态修改需要重新渲染页面时,却发现这个DOM节点不存在了,那么就会造成React的崩溃,因为React不知道怎么去处理一个已经删除了的DOM节点。所以在使用Ref操作DOM时,咱们应该尽量避免对React管理的元素进行修改、添加子元素、从中删除子元素会导致不一致的视觉结果,否则就会造成React的崩溃。当然,如果你清楚你那么做并不会影响React后续更新的部分DOM,还是可以进行这样的操作的。例如,如果某些<div>JSX中始终为空,React将没有理由去变动其子列表。 因此,在那里手动增删元素是安全的。

通过useMemouseCallbackuseRef钩子的记忆化功能,咱们可以避免很多不必要的计算、防止不必要的重新渲染,并在渲染之间保留值和引用,以此优化React应用程序的性能。这可以带来更流畅的用户体验和更高效的资源使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/10 18:24:58

13、Linux Mint 软件安装、更新与多媒体使用指南

Linux Mint 软件安装、更新与多媒体使用指南 1. 软件维护与高级包管理 在软件维护方面,有两个选项值得关注:修复合并列表问题和清除残留配置。这两个选项可用于解决后续可能遇到的错误信息,但在正常使用中,一般不会遇到这些问题。若遇到合并列表相关的错误信息,可使用修…

作者头像 李华
网站建设 2026/4/9 11:48:02

【LeetCode刷题】缺失的第一个正数

给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,0] 输出&#xff1a;3 解释&#xff1a;范围 [1,2] 中的数字都在数组…

作者头像 李华
网站建设 2026/4/11 12:19:18

api vs jsp 绑定风格

api vs jsp 绑定风格 这是一个关于 Java Web Servlet 接口的示例&#xff0c;我将为您创建两个 Servlet&#xff1a; 一个支持 cURL 或任何标准 HTTP 客户端调用的接口 (CurlCallableServlet)。一个通常不直接设计为 cURL 调用&#xff0c;而是与 JSP 页面集成&#xff08;用于…

作者头像 李华
网站建设 2026/4/8 8:30:19

Java毕设项目:基于springboot厨具厂产品在线销售系统设计与实现小程序(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/10 6:23:55

League Akari:6个实用功能让你告别繁琐操作,轻松上分

League Akari&#xff1a;6个实用功能让你告别繁琐操作&#xff0c;轻松上分 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari …

作者头像 李华
网站建设 2026/4/10 15:06:31

QDialog-基础讲解

注意&#xff1a;前面都是基础讲解&#xff0c;如果有什么不懂的可以看看&#xff0c;但是如果只是想看实际运用场景&#xff0c;建议只看自定义案例 这里是目录标题自定义案例问题互动案例互动The QDialog class is the base class of dialog windowsQDialog 类是对话框窗口的…

作者头像 李华