在 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项目按需加载报错 .libraryName is not a valid Plugin property

    babel presets react app plugins import libraryName antd style true 原配置如上会报错 libraryName is not a valid Plugin property g
  • Antd DatePicker 设置默认值报clone.weekday is not a function

    代码 dayjs版本1 11 7 页面 当点击页面日期框会报clone weekday is not a function 解决方法 在jsx文件中添加如下js import dayjs from dayjs import advanced
  • React Router 路由守卫

    React Router 路由守卫 组件内路由守卫 1 下面是使用高阶组件实现路由守卫的示例代码 import React from react import Route Redirect from react router dom con
  • react的条件渲染(或者组件渲染)五种方式 --开发基础总结

    1 使用if的方式判断是否渲染某个组件 function UserGreeting props return h1 Welcome back h1 function GuestGreeting props return h1 Please
  • React的State Hook用法详解

    一 State Hook是啥 State Hook 就是指 useState 这个特殊函数 让你不用编写class 就可以使用state特性 换言之就是让 函数组件 拥有 state 特性 对数据进行动态更新 二 class中的state
  • 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
  • Ionic3开发教程 - 开发(2)

    Ionic3开发系列教程Ionic3开发教程 环境准备 1 Ionic3开发教程 开发 2 Ionic3开发教程 发布Android版本 3 Ionic3开发教程 发布IOS版本 4 Ionic3开发教程 更新 5 本文中介绍的Ionic3
  • 三分钟实现一个react-router-dom5.0的路由拦截(导航守卫)

    不同于vue 通过在路由里设置meta元字符实现路由拦截 在使用 Vue 框架提供了路由守卫功能 用来在进入某个路有前进行一些校验工作 如果校验失败 就跳转到 404 或者登陆页面 比如 Vue 中的 beforeEnter 函数 rout
  • React路由懒加载的实现

    React lazy 通过引入lazy Suspense两个方法实现路由懒加载 首先 我们需要在组件中引入lazy Suspense这两个方法 然后我们需要通过Suspense组件 包裹着注册路由 import React Componen
  • react组件状态同步-状态提升

    假设定义组件TemperatureInputSon import React from react class TemperatureInputSon extends React Component constructor props su
  • React(一):React的设计哲学 - 简单之美

    React 一 React的设计哲学 简单之美 React 二 React开发神器Webpack React 三 理解JSX和组件 React 四 虚拟DOM Diff算法解析 React 五 使用Flux搭建React应用程序架构 Rea
  • React解密:React Hooks函数之useCallback和useMemo

    之所以将useCallback和useMemo放到一起 从某种意义上说 他们都是性能优化的始作俑者 他们也有很多的共性 我们先来回顾一下class组件性能优化的点 调用 setState 就会触发组件的重新渲染 无论前后 state 是否相
  • useEffect详情用法

    1 为什么要使用useEffect 想必大家都是用过vue吧 在vue框架所写的项目中 我们通过在与后端进行数据交互的过程中 通常都是会在生命周期中进行数据的请求 然后将数据返回给页面进行渲染 在React中我们也是这样 但是在函数式组件中
  • 前端学科面试题大全

    作用域和值类型引用类型的传递 变量作用域 作用域变量访问区域 变量值存在栈中 变量赋值相当于值赋值 值传递与引用传递有哪些区别 函数内部 变量会先声明 形式参数变量声明提升 整个函数体有var声明的变量 如果没有访问全局定义的num2 函数
  • react 上传文件(多选)功能入的坑

    1 这里报错是因为onChange的this指向不对 解决方法在constructor中写 this onChange this onChange bind this 或者在绑定事件的时候写 onChange this onChange b
  • react 父组件调用子组件的方法

    子组件中 const child forwardRef props ref gt useImperativeHandle ref gt 这里面的方法是暴露给父组件的 test console log 我是组件里的test方法 test2 t
  • 自定义hooks

    自定义传参hooks 把大多数的通用代码 或者props 抽成一个hooks 用hooks实现上下文 就不用再一一传参了 实现原理 主要是通过createContext useContext 生产 消费者模式 用于大量props一层一层传参
  • React中渲染html结构---dangerouslySetInnerHTML

    dangerouslySetInnerHTML 胡子 语法绑定的内容全部作为普通文本渲染 渲染html结构基于 dangerouslySetInnerHTML dangerouslySetInnerHTML 是 React 标签的一个属性
  • React Jsx转换成真实DOM过程?

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

随机推荐