聊聊怎么利用Memoization提高React性能

本篇文章带大家了解一下memoization,介绍一下为什么需要 memoization,以及 react中实现 memoization以提高性能的方法,希望对大家有所帮助!

聊聊怎么利用Memoization提高React性能

在本教程中,我们将学习如何在 React 中实现 Memoization。Memoization 通过缓存函数调用的结果并在再次需要时返回这些缓存的结果来提高性能。

我们将介绍以下内容:

React 是如何渲染视图的?为什么需要 Memoization?如何在函数组件和类组件中实现 Memoization?注意事项

本文假设你对 React 中的类和函数组件有基本的了解。

如果你想查阅这些主题,可以查看 React 官方文档 components and props

https://reactjs.org/docs/components-and-props.html

1.png

React 是如何渲染视图的?

在讨论 React 中的 Memoization 细节之前,让我们先来看看 React 是如何使用虚拟 DOM 渲染 UI 的。【相关推荐:Redis视频教程】

常规 DOM 基本上包含一组用树的形式保存的节点。DOM 中的每个节点代表一个 UI 元素。每当应用程序中出现状态变更时,该 UI 元素及其所有子元素的相应节点都会在 DOM 树中更新,然后会触发 UI 重绘。

在高效的 DOM 树算法的帮助下,更新节点的速度更快,但重绘的速度很慢,并且当该 DOM 具有大量 UI 元素时,可能会影响性能。因此,在 React 中引入了虚拟 DOM。

这是真实 DOM 的虚拟表示。现在,每当应用程序的状态有任何变化时,React 不会直接更新真正的 DOM,而是创建一个新的虚拟 DOM。然后 React 会将此新的虚拟 DOM 与之前创建的虚拟 DOM 进行比较,找到有差异的地方(译者注:也就是找到需要被更新节点),然后进行重绘。

根据这些差异,虚拟 DOM 能更高效地更新真正的 DOM。这样提高了性能,因为虚拟 DOM 不会简单地更新 UI 元素及其所有子元素,而是有效地仅更新实际 DOM 中必要且最小的更改。

为什么需要 Memoization?

在上一节中,我们看到了 React 如何使用虚拟 DOM 有效地执行 DOM 更新操作来提高性能。在本节中,我们将介绍一个例子,该例子解释了为了进一步提高性能而需要使用 Memoization。

我们将创建一个父类,包含一个按钮,用于递增名为 count 的变量。父组件还调用了子组件,并向其传递参数。我们还在 render 方法中添加了 console.log() 语句:

//Parent.jsclass Parent extends React.Component {  constructor(props) {    super(props);    this.state = { count: 0 };  }  handleClick = () => {    this.setState((prevState) => {      return { count: prevState.count + 1 };    });  };  render() {    console.log("Parent render");    return (      

{this.state.count}

); }}export default Parent;

此示例的完整代码可在 CodeSandbox 上查看。

我们将创建一个 Child 类,该类接受父组件传递的参数并将其显示在 UI 中:

//Child.jsclass Child extends React.Component {  render() {    console.log("Child render");    return (      

{this.props.name}

); }}export default Child;

每当我们点击父组件中的按钮时,count 值都会更改。由于 state 变化了,因此父组件的 render 方法被执行了。

传递给子组件的参数在每次父组件重新渲染时都没有改变,因此子组件不应重新渲染。然而,当我们运行上面的代码并继续递增 count 时,我们得到了以下输出:

Parent renderChild renderParent renderChild renderParent renderChild render

你可以在这个 sandbox 中体验上述示例,并查看控制台的输出结果。

从输出中,我们可以看到,当父组件重新渲染时,即使传递给子组件的参数保持不变,子组件也会重新渲染。这将导致子组件的虚拟 DOM 与以前的虚拟 DOM 执行差异检查。由于我们的子组件中没有变更且重新渲染时的所有 props 都没有变,所以真正的 DOM 不会被更新。

真正的 DOM 不会进行不必要地更新对性能确实是有好处,但是我们可以看到,即使子组件中没有实际更改,也会创建新的虚拟 DOM 并执行差异检查。对于小型 React 组件,这种性能消耗可以忽略不计,但对于大型组件,性能影响会很大。为了避免这种重新渲染和虚拟 DOM 的差异检查,我们使用 Memoization。

React 中的 Memoization

在 React 应用的上下文中,Memoization 是一种手段,每当父组件重新渲染时,子组件仅在它所依赖的 props 发生变化时才会重新渲染。如果子组件所依赖的 props 中没有更改,则它不会执行 render 方法,并将返回缓存的结果。由于渲染方法未执行,因此不会有虚拟 DOM 创建和差异检查,从而实现性能的提升。

现在,让我们看看如何在类和函数组件中实现 Memoization,以避免这种不必要的重新渲染。

类组件实现 Memoization

为了在类组件中实现 Memoization,我们将使用 React.PureComponent。React.PureComponent 实现了 shouldComponentUpdate(),它对 stateprops 进行了浅比较,并且仅在 props 或 state 发生更改时才重新渲染 React 组件。

将子组件更改为如下所示的代码:

//Child.jsclass Child extends React.PureComponent { // 这里我们把 React.Component 改成了 React.PureComponent  render() {    console.log("Child render");    return (      

{this.props.name}

); }}export default Child;

此示例的完整代码显示在这个 sandbox 中。

父组件保持不变。现在,当我们在父组件中增加 count 时,控制台中的输出如下所示:

Parent renderChild renderParent renderParent render

对于首次渲染,它同时调用父组件和子组件的 render 方法。

对于每次增加 count 后的重新渲染,仅调用父组件的 render 函数。子组件不会重新渲染。

函数组件实现 Memoization

为了在函数组件中实现 Memoization,我们将使用 React.memo()。React.memo() 是一个高阶组件(HOC),它执行与 PureComponent 类似的工作,来避免不必要的重新渲染。

极简智能王 极简智能王

极简智能- 智能聊天AI绘画,还可以创作、编写、翻译、写代码等多种功能,满足用户生活和工作的多方面需求

极简智能王 33 查看详情 极简智能王

以下是函数组件的代码:

//Child.jsexport function Child(props) {  console.log("Child render");  return (    

{props.name}

);}export default React.memo(Child); // 这里我们给子组件添加 HOC 实现 Memoization

同时还将父组件转换为了函数组件,如下所示:

//Parent.jsexport default function Parent() {  const [count, setCount] = useState(0);  const handleClick = () => {    setCount(count + 1);  };  console.log("Parent render");  return (    

{count}

);}

此示例的完整代码可以在这个 sandbox 中看到。

现在,当我们递增父组件中的 count 时,以下内容将输出到控制台:

Parent renderChild renderParent renderParent renderParent render

React.memo() 存在的问题

在上面的示例中,我们看到,当我们对子组件使用 React.memo() HOC 时,子组件没有重新渲染,即使父组件重新渲染了。

但是,需要注意的一个小问题是,如果我们将函数作为参数传递给子组件,即使在使用 React.memo() 之后,子组件也会重新渲染。让我们看一个这样的例子。

我们将更改父组件,如下所示。在这里,我们添加了一个处理函数,并作为参数传递给子组件:

//Parent.jsexport default function Parent() {  const [count, setCount] = useState(0);  const handleClick = () => {    setCount(count + 1);  };  const handler = () => {    console.log("handler");    // 这里的 handler 函数将会被传递给子组件  };  console.log("Parent render");  return (    

{count}

);}

子组件代码将保持原样。我们不会在子组件中使用父组件传递来的函数:

//Child.jsexport function Child(props) {  console.log("Child render");  return (    

{props.name}

);}export default React.memo(Child);

现在,当我们递增父组件中的 count 时,它会重新渲染并同时重新渲染子组件,即使传递的参数中没有更改。

那么,是什么原因导致子组件重新渲染的呢?答案是,每次父组件重新渲染时,都会创建一个新的 handler 函数并将其传递给子组件。现在,由于每次重新渲染时都会重新创建 handle 函数,因此子组件在对 props 进行浅比较时会发现 handler 引用已更改,并重新渲染子组件。

接下来,我们将介绍如何解决此问题。

通过 useCallback() 来避免更多的重复渲染

导致子组件重新渲染的主要问题是重新创建了 handler 函数,这更改了传递给子组件的引用。因此,我们需要有一种方法来避免这种重复创建。如果未重新创建 handler 函数,则对 handler 函数的引用不会更改,因此子组件不会重新渲染。

为了避免每次渲染父组件时都重新创建函数,我们将使用一个名为 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有关 Hooks 的更多信息,你可以查看 React 的官方 CodeSandbox0,或者查看 `CodeSandbox1″。

useCallback() 钩子传入两个参数:回调函数和依赖项列表。

以下是 useCallback() 示例:

const handleClick = useCallback(() => {  //Do something}, [x,y]);

在这里,useCallback() 被添加到 handleClick() 函数中。第二个参数 [x, y] 可以是空数组、单个依赖项或依赖项列表。每当第二个参数中提到的任何依赖项发生更改时,才会重新创建 handleClick() 函数。

如果 useCallback() 中提到的依赖项没有更改,则返回作为第一个参数提及的回调函数的 Memoization 版本。我们将更改父组件,以便对传递给子组件的处理程序使用 useCallback() 钩子:

//Parent.jsexport default function Parent() {  const [count, setCount] = useState(0);  const handleClick = () => {    setCount(count + 1);  };  const handler = useCallback(() => { // 给 handler 函数使用 useCallback()    console.log("handler");  }, []);  console.log("Parent render");  return (    

{count}

);}

子组件代码将保持原样。

此示例的完整代码这个 CodeSandbox2 中。

当我们在上述代码的父组件中增加 count 时,我们可以看到以下输出:

Parent renderChild renderParent renderParent renderParent render

由于我们对父组件中的 handler 使用了 useCallback() 钩子,因此每次父组件重新渲染时,都不会重新创建 handler 函数,并且会将 handler 的 Memoization 版本传递到子组件。子组件将进行浅比较,并注意到 handler 函数的引用没有更改,因此它不会调用 render 方法。

值得注意的事

Memoization 是一种很好的手段,可以避免在组件的 state 或 props 没有改变时对组件进行不必要的重新渲染,从而提高 React 应用的性能。你可能会考虑为所有组件添加 Memoization,但这并不一定是构建高性能 React 组件的方法。只有在组件出现以下情况时,才应使用 Memoization:

固定的输入有固定的输出时具有较多 UI 元素,虚拟 DOM 检查将影响性能多次传递相同的参数

总结

在本教程中,我们理解了:

React 是如何渲染 UI 的为什么需要 Memoization如何在 React 中通过函数组件的 React.memo() 和类组件的 React.PureComponent 实现 Memoization通过一个例子展示,即使在使用 React.memo() 之后,子组件也会重新渲染如何使用 useCallback() 钩子来避免在函数作为 props 传递给子组件时产生重新渲染的问题

希望这篇 React Memoization 的介绍对你有帮助!

原文地址:https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/

原文作者:Nida Khan

更多编程相关知识,请访问:CodeSandbox3!!

以上就是聊聊怎么利用Memoization提高React性能的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/552042.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Linux DHCP与静态IP的区别
上一篇 2025年11月9日 20:35:12
最佳电脑联机游戏推荐:畅享无缝连接与团队合作的乐趣
下一篇 2025年11月9日 20:35:23

相关推荐

  • React组件中动态属性值的管理与同步:利用状态实现受控组件

    本教程旨在解决react组件中动态属性值同步使用的问题。我们将探讨如何利用react的`usestate` hook来管理组件内部状态,从而实现一个属性的值动态地影响另一个属性,并构建出可预测、易于维护的受控组件。文章将通过具体代码示例,详细阐述从初始化状态到处理状态更新的完整过程,并强调受控组件在…

    2026年5月10日
    000
  • 基于两数组数据计算结果排序的 React 教程

    本教程针对 React 应用中需要根据两个独立数组的数据计算结果进行排序的场景,提供了一种高效的解决方案。通过使用 JavaScript 的 `reduce` 和 `map` 方法,将两个数组根据唯一标识符进行合并,从而简化排序逻辑,提高代码的可读性和可维护性。避免了复杂的嵌套循环或同步迭代,提供了…

    2026年5月10日
    000
  • 解决React中按钮点击不显示弹出表单的问题:状态管理与语法修正

    本教程旨在解决react应用中点击按钮后弹出表单未能正确渲染的问题。核心在于识别并修正代码中的语法错误以及未定义的react状态管理函数。我们将详细探讨如何使用`usestate`等react hooks来声明和管理组件状态,确保交互逻辑的正确实现,并提供结构清晰的代码示例,帮助开发者构建功能完善的…

    2026年5月10日
    000
  • Go语言与Microsoft SharePoint集成指南

    Go语言可以有效集成Microsoft SharePoint,主要通过两种途径:一是利用SharePoint提供的RESTful API进行数据交互,Go的标准HTTP客户端库即可轻松实现;二是通过SharePoint应用模型开发自托管应用,这种模型支持使用包括Go在内的任何语言编写后端逻辑。 1.…

    2026年5月10日
    000
  • javascript生命周期钩子是什么_组件有哪些关键阶段?

    JavaScript原生无生命周期钩子,这是Vue、React等框架为组件设计的机制;Vue按创建、挂载、更新、卸载四阶段提供对应钩子,React类组件有明确生命周期方法,函数组件则通过useEffect模拟,其核心价值在于精准控制执行时机以避免DOM操作错误和内存泄漏。 JavaScript 本身…

    2026年5月10日
    000
  • React Redux 中 useSelector 的自动订阅与取消订阅机制

    React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制

    本文深入探讨 react redux 中 `useselector` hook 的核心机制。它详细解释了 `useselector` 如何在组件挂载时自动订阅 redux store 的状态更新,并在组件卸载时智能地取消订阅。这确保了应用程序的性能和内存效率,避免了对已卸载组件进行不必要的更新,从而…

    2026年5月10日 用户投稿
    100
  • 在 React 中实现用户输入停止检测的防抖策略

    本文详细介绍了在 React 应用中如何精确检测用户停止输入行为。通过引入防抖(Debounce)函数,可以有效优化输入事件处理,避免频繁触发不必要的网络请求或状态更新。文章提供了基于 React Hooks 的防抖实现示例,并探讨了其在提升用户体验和系统性能方面的应用,确保在用户停止输入指定时间后…

    用户投稿 2026年5月10日
    000
  • JavaScript中的标签模板字面量(Tagged Templates)有哪些高级用法?

    标签模板通过自定义函数实现复杂逻辑,如html函数转义防止XSS,css函数生成唯一类名封装样式,结合哈希值隔离组件样式,确保安全与模块化。 标签模板字面量不只是字符串拼接工具,它能结合函数实现更复杂的逻辑处理。通过自定义标签函数,你可以解析模板中的表达式和静态部分,从而实现如国际化、样式封装、安全…

    2026年5月10日
    000
  • 深入理解React组件命名规范:解决组件不渲染的常见陷阱

    本教程深入探讨react组件命名约定在组件渲染中的关键作用。我们将解释为何自定义组件名必须以大写字母开头(pascalcase),以避免与原生html元素混淆。通过对比错误和正确的代码示例,教程将指导开发者如何遵循这一核心规范,从而解决组件不显示、`is defined but never used…

    2026年5月10日
    000
  • 优化React-Redux应用中的用户与受保护数据按需加载

    本教程旨在解决React-Redux应用中用户数据和受保护API密钥在用户未登录时仍被请求,导致401错误的问题。通过引入条件性Redux状态初始化和动作分发逻辑,确保只有在用户被认为已认证时才发起相关的API请求,从而优化应用性能,减少不必要的网络流量和控制台错误。 在构建现代Web应用时,尤其是…

    2026年5月10日
    000
  • 全栈JS代码怎么结构化_全栈JavaScript项目代码结构与规范指南

    采用分层+功能划分的目录结构,明确分离前后端代码;2. 遵循单一职责原则,路由、控制器、服务与模型各司其职;3. 统一命名规范并集成ESLint+Prettier保证代码风格一致;4. 使用环境变量管理配置,通过脚本实现自动化构建与并发启动服务。 全栈JavaScript项目涉及前端、后端、数据库交…

    2026年5月10日
    000
  • JavaScript模块加载机制_JavaScript代码组织规范

    现代前端推荐使用ES Modules,通过import和export实现静态依赖管理,配合合理目录结构与命名规范提升可维护性,注意浏览器与Node.js的运行差异。 JavaScript 的模块加载机制和代码组织规范是现代前端开发中的核心基础。随着项目规模扩大,良好的模块化设计能提升代码可维护性、复…

    2026年5月10日
    000
  • 前端实现记住密码与自动填充_javascript技巧

    正确使用表单标签与属性、支持“记住我”功能、避免破坏自动填充机制、测试浏览器兼容性可实现稳定自动填充。1. 使用标准input类型并设置autocomplete属性为username和current-password;2. 登录成功后通过localStorage保存用户名,页面加载时恢复;3. 避免…

    2026年5月10日
    000
  • 深入理解React中Refs、DOM组件与类组件实例的Ref转发机制

    本文旨在澄清react中“dom组件”的概念,并深入探讨refs在原生dom元素和自定义组件(特别是类组件实例)之间的转发机制。我们将解析官方文档中的常见困惑,并通过示例代码演示如何正确地将refs转发给不同的组件类型,从而帮助开发者更好地利用refs进行dom或组件实例的直接操作。 在React开…

    2026年5月10日
    000
  • Redux Dispatch 不更新状态的排查与解决

    本文旨在帮助开发者诊断和解决 Redux 应用中 dispatch 函数调用后状态未更新的问题。通过分析常见的错误配置和代码实现,提供逐步排查方案和修正建议,确保 Redux 状态管理的正确性和可靠性。 在 Redux 应用开发中,dispatch 函数用于触发状态变更,如果 dispatch 调用…

    2026年5月10日
    100
  • React中正确处理Select元素OnChange事件

    在React应用中,正确监听select下拉菜单的值变化是常见的需求。本文将详细阐述,与原生HTML的onchange属性不同,React中应使用驼峰命名法的onChange属性来捕获此类事件。我们将通过示例代码演示如何结合React的状态管理,实现对select元素值的有效监听和响应,确保组件行为…

    2026年5月10日
    100
  • JavaScript中动态生成HTML链接:正确使用模板字面量嵌入URL

    本文深入探讨了在javascript中动态生成html链接时,如何正确地将变量(尤其是url)嵌入到`href`属性中。通过分析常见的错误,即混淆javascript的模板字面量与框架特有的模板语法,文章详细演示了使用es6模板字面量`${}`进行字符串插值的正确方法,确保动态链接能够被浏览器正确解…

    2026年5月10日
    000
  • HTML代码怎么实现错误边界_HTML代码错误边界处理方法与异常捕获策略

    答案:通过JavaScript模拟错误边界,结合try…catch、onerror事件、Promise.catch()及全局监控工具,可有效捕获并隔离HTML应用中的错误,防止功能失效。 HTML本身并没有直接提供像JavaScript那样的“错误边界”概念。HTML主要负责结构和内容,…

    2026年5月10日
    100
  • 在JavaScript中,如何实现数据的不可变性(Immutability)?

    使用const声明变量可防止重新赋值,但无法阻止对象内部修改,需结合扩展运算符、不可变数组方法和Object.freeze实现深层不可变,关键在于始终返回新对象而非修改原数据。 在JavaScript中,实现数据的不可变性意味着避免直接修改现有对象或数组,而是通过创建新对象来反映状态变化。虽然Jav…

    2026年5月10日
    200
  • 解决 React Native Android 应用启动时出现的伪启动页问题

    本文旨在解决 React Native Android 应用在启动时,先显示一个带有应用图标的黑色伪启动页,然后再显示自定义启动页的问题。通过修改 Android 项目的 `styles.xml` 文件,禁用应用的预览窗口,即可有效避免此问题,提升用户体验。 在开发 React Native 应用时…

    2026年5月10日
    100

发表回复

登录后才能评论
关注微信