在 React 中实现记忆以提高性能小技巧

2023-05-16

React 如何渲染 UI

在详细了解 React 中的 memoization 之前,让我们先看看 React 如何使用虚拟 DOM 呈现 UI。

常规 DOM 基本上包含一组表示为树的节点。DOM 中的每个节点都是 UI 元素的表示。每当您的应用程序中发生状态更改时,该 UI 元素的相应节点及其所有的节点都会在 DOM 中文更新,然后重新绘制 UI 以反映更新后的更改。

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

这是真实 DOM 的虚拟表示。现在,每当应用程序的状态发生任何变化时,React 都会创建一个新的虚拟 DOM,而不是直接更新真实 DOM。React 然后将这个新的虚拟 DOM 与之前创建的虚拟 DOM 进行比较,以找出需要重新绘制的差异。

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

为什么我们需要 React 中的记忆

在上一节中,我们看到了 React 如何使用虚拟 DOM 有效地执行 DOM 更新以提高性能。在本节中,我们将查看一个用例,该用例解释了记忆化以进一步提高性能的必要性。

我们将创建一个父类,其中包含一个按钮来增加一个名为 的状态变量count。父组件也有一个对子组件的调用,将一个 prop 传递给它。我们还在console.log()render 两个类的方法中添加了语句:

//Parent.js
class 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 (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

此示例的完整代码可在CodeSandbox上找到。

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

//Child.js
class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

每当我们单击父组件中的按钮时,计数值都会发生变化。由于这是状态变化,因此调用了父组件的 render 方法。

每次父重新渲染时,传递给子类的道具保持不变,因此子组件不应重新渲染。然而,当我们运行上述代码并不断增加计数时,我们会得到以下输出:

Parent render
Child render
Parent render
Child render
Parent render
Child render

您可以在以下沙箱中自己增加上述示例的计数,并查看控制台的输出:

从这个输出中,我们可以看到,当父组件重新渲染时,它也会重新渲染子组件——即使传递给子组件的道具没有改变。这将导致孩子的虚拟 DOM 与之前的虚拟 DOM 进行差异检查。由于我们在子组件中没有区别——因为所有重新渲染的 props 都是相同的——所以真正的 DOM 没有更新。

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

React 中的记忆

在 React 应用程序的上下文中,memoization 是一种技术,当父组件重新渲染时,子组件仅在 props 发生变化时重新渲染。如果 props 没有变化,则不会执行 render 方法,会返回缓存的结果。由于未执行 render 方法,因此不会有虚拟 DOM 创建和差异检查——从而给我们带来了性能提升。

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

在类组件中实现记忆

为了在类组件中实现记忆,我们将使用React.PureComponent。React.PureComponent实现shouldComponentUpdate(),它对 state 和 props 进行浅层比较,并仅在 props 或 state 发生变化时渲染 React 组件。

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

//Child.js
class Child extends React.PureComponent { // Here we change React.Component to React.PureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

此示例的完整代码显示在以下沙箱中:

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

Parent render
Child render
Parent render
Parent render

对于第一次渲染,它调用父组件和子组件的渲染方法。

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

在功能组件中实现记忆

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

以下是功能组件的代码:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); // Here we add HOC to the child component for memoization

我们还将父组件转换为功能组件,如下所示:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} />
    </div>
  );
}

此示例的完整代码可以在以下沙箱中看到:

现在,当我们增加父组件中的计数时,控制台会输出以下内容:

Parent render
Child render
Parent render
Parent render
Parent render

函数道具的 React.memo() 问题

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

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

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

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    // This is the new handler that will be passed to the child
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

的组件代码保持原样。我们不使用我们在子组件中作为 props 传递的函数:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);

现在,当我们增加父组件中的计数时,它会重新渲染并重新渲染子组件,即使传递的道具没有变化。

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

在下一节中,我们将了解如何解决此问题。

useCallback()避免进一步重新渲染

导致子级重新渲染的主要问题是处理函数的重新创建,它改变了传递给子级的引用。所以,我们需要有办法避免这种娱乐。如果未重新创建处理程序,则对处理程序的引用不会改变——因此子进程不会重新渲染。

为了避免每次渲染父组件时都重新创建函数,我们将使用一个名为useCallback()的 React 钩子。Hooks 是在 React 16 中引入的。要了解有关 Hooks 的更多信息,您可以查看 React 的官方Hooks文档,或查看“ React Hooks: How to Get Started & Build Your Own ”。

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

考虑以下useCallback() 示例:

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

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

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

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { //using useCallback() for the handler function
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

子组件代码保持原样。

此示例的完整代码如下所示:

当我们为上面的代码增加父组件中的计数时,我们可以看到以下输出:

Parent render
Child render
Parent render
Parent render
Parent render

由于我们为父处理程序使用了useCallback()钩子,因此每次父处理程序重新渲染时,处理程序函数都不会重新创建,并且处理程序的记忆版本被发送给子处理程序。子组件将进行浅层比较,并注意到处理函数的引用没有改变——因此它不会调用该render方法。

要记住的事情

记忆化是一种提高 React 应用程序性能的好技术,如果组件的 props 或 state 没有改变,它可以避免不必要的重新渲染组件。你可能会想到只为所有组件添加 memoization,但这不是构建 React 组件的好方法。你应该只在没有组件的情况下使用记忆:

  • 给定相同的道具时返回相同的输出
  • 有多个 UI 元素,虚拟 DOM 检查会影响性能
  • 经常提供相同的道具

如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,,咱们下期见。

收藏 等于白嫖,点赞才是真情。

亲爱的小伙伴们,有需要JAVA面试文档资料请点赞+转发,关注我后,私信我333就可以领取免费资料哦

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 React 中实现记忆以提高性能小技巧 的相关文章

  • React中的“计算属性”

    React中的 计算属性 相信许多学习过vue的小伙伴对计算属性都不陌生吧 计算属性能帮我们数据进行一些计算操作 计算属性是依赖于data里面的数据的 在vue中只要计算属性依赖的data值发生改变 则计算属性就会调用 那React中也有计
  • 函数式组件与类组件有何不同?

    与React类组件相比 React函数式组件究竟有何不同 在过去一段时间里 典型的回答是类组件提供了更多的特性 比如state 当有了Hooks后 答案就不再是这样了 或许你曾听过它们中的某一个在性能上的表现优于另一个 那是哪一个 很多此类
  • React Native 环境搭建, 新建项目, 运行和调试

    React Native 可以理解为一个基于 JavaScript 具备动态配置能力 面向前端开发者的移动端开发框架 目前为止虽然一直还没有V1 0 0版本 但是相信很多小伙伴都了解过或者已经入坑了 为什么RN那么有人气呢 我们可以先简单分
  • 【React】路由(详解)

    目录 单页应用程序 SPA 路由 前端路由 后端路由 路由的基本使用 使用步骤 常用组件说明 BrowserRouter和HashRouter的区别 路由的执行过程 默认路由 精确匹配 Switch的使用 重定向路由 嵌套路由 向路由组件传
  • ant design pro v5 配置拦截器,header

    ant design pro v5 配置拦截器 header 1 资料文档 https umijs org zh CN plugins plugin request requestinterceptors 2 编写app tsx 我这里是自
  • 对 React Hook的闭包陷阱的理解,有哪些解决方案?

    hooks中 奇怪 其实符合逻辑 的 闭包陷阱 的场景 同时 在许多 react hooks 的文章里 也能看到 useRef 的身影 那么为什么使用 useRef 又能摆脱 这个 闭包陷阱 搞清楚这些问题 将能较大的提升对 react h
  • vue 全局组件注册_如何注册vue3全局组件

    vue 全局组件注册 With the new versions of Vue3 out now it s useful to start learning how the new updates will change the way w
  • 如何在 Ubuntu 20.04 上使用 React 前端设置 Ruby on Rails v7 项目

    作者选择了电子前沿基金会接受捐赠作为为捐款而写程序 介绍 红宝石 on Rails是一个流行的服务器端 Web 应用程序框架 它为当今网络上存在的许多流行应用程序提供支持 例如GitHub Basecamp 声云 Airbnb and Tw
  • Ionic3开发教程 - 开发(2)

    Ionic3开发系列教程Ionic3开发教程 环境准备 1 Ionic3开发教程 开发 2 Ionic3开发教程 发布Android版本 3 Ionic3开发教程 发布IOS版本 4 Ionic3开发教程 更新 5 本文中介绍的Ionic3
  • React页面设计初体验

    1 定制路由 export default login path login name login component layouts BlankLayout routes path login component Login Index
  • 组件间样式覆盖问题--CSS Modules

    1 组件间样式覆盖问题 问题 CityList 组件的样式 会影响 Map 组件的样式 原因 在配置路由时 CityList 和 Map 组件都被导入到项目中 那么组件的样式也就被导入到项目中了 如果组件之间样式名称相同 那么一个组件中的样
  • Notion笔记搭建博客网站 - NotionNext

    NotionNext是什么 NotionNext是我在Github上开源的基于Next js框架开发的博客生成器 目的是帮助写作爱好者们通过Notion笔记快速搭建一个独立站 从而专注于写作 而不需要操心网站的维护 它将您的Notion笔记
  • 关于Vue.js和React.js,听听国外的开发者怎么说?

    VueJS 与 ReactJS 到底怎么样如何 听听别人怎么说 使用所有新的库和框架 很难跟上所有这些库和框架 也就是说 这就需要您决定哪些是值得花时间的 让我们看看人们说什么 和Vue JS一起工作是很愉快的 我发现学习曲线很浅 然而 这
  • React的超详细讲解

    React React的重点 webpack webpack 是一个现代 JavaScript 应用程序的静态模块打包器 module bundler 当 webpack 处理应用程序时 它会递归地构建一个依赖关系图 dependency
  • react 上传文件(多选)功能入的坑

    1 这里报错是因为onChange的this指向不对 解决方法在constructor中写 this onChange this onChange bind this 或者在绑定事件的时候写 onChange this onChange b
  • React官方文档--Lifting State Up

    Lifting State Up 如果 几个组件需要同时影响并且修改同一个数据 我们建议将这个共享状态进行提升 给他们最近的共同祖先 在这个部分 我们将要创建一个温度计算器来判断水会不会在给定温度下沸腾 我们将从一个叫做BoilingVer
  • umi 后台管理demo

    umi 后台管理demo umi react ts dva antd egg 有待优化 简单的前后端管理demo 接口提供增删查改 前端也有相应功能 github代码 https github com huiBuiling ql admin
  • react 父组件调用子组件的方法

    子组件中 const child forwardRef props ref gt useImperativeHandle ref gt 这里面的方法是暴露给父组件的 test console log 我是组件里的test方法 test2 t
  • React配置@src根路径

    第一种 直接修改node modules包中的webpack config js文件 找到node modules react scripts config webpack config js文件 修改其中alias中的配置 添加 src
  • React Jsx转换成真实DOM过程?

    面试官 说说React Jsx转换成真实DOM过程 一 是什么 react 通过将组件编写的 JSX 映射到屏幕 以及组件中的状态发生了变化之后 React 会将这些 变化 更新到屏幕上 在前面文章了解中 JSX 通过 babel 最终转化

随机推荐