关于react.js:Reactmemo-解决了什么问题

2次阅读

共计 3855 个字符,预计需要花费 10 分钟才能阅读完成。

React.memo 解决了什么问题?

  • 原文地址 –DapanDocs:https://skillgroup.cn/framework/react/hooks/memo.html

React.memo 是 React 16.6 版本引入的一个新性能,它是一个高阶组件,旨在优化函数组件的性能。那么,它到底解决了什么问题呢?

React.memo 根底用法

问题:不必要的渲染

在 React 中,组件的从新渲染通常是因为状态或 props 的变动引起的。但有时,即便相干数据没有发生变化,组件也可能会进行不必要的渲染。这种不必要的渲染可能会导致性能降落,尤其是在简单的应用程序中。

<div ref=”memo1″ />

::: details demo 代码
<<< @/components/react/hooks/memo/BasicComponent.jsx
:::

在这个例子中,ParentComponent有一个状态 count,每当咱们点击按钮时,这个状态就会减少。因为 React 的工作形式,每次count 发生变化时,ParentComponent都会从新渲染。这也意味着 ChildComponent 也会从新渲染,只管传递给它的text prop 并没有产生任何变动。

这就是一个不必要的渲染的例子。每次 ParentComponent 的状态发生变化时,ChildComponent都会进行不必要的渲染,即便它接管的 props 完全相同。

这种状况在大型应用程序中可能会变得更加重大,因为不必要的渲染可能会在多个组件之间产生连锁反应,导致整个应用程序的性能降落。

解决方案:浅比拟 props

React.memo 的工作原理是对组件的 props 进行浅比拟。如果传递给组件的 props 没有发生变化,React.memo 会复用上一次的渲染后果,从而防止不必要的渲染。

<div ref=”memo2″ />

::: details demo 代码
<<< @/components/react/hooks/memo/MemoizedComponent.jsx
:::

在上述例子中,即便 ParentComponent 从新渲染(例如,因为 count 状态的变动),因为传递给 MemoizedChildComponenttext prop 没有发生变化,ChildComponent 不会从新渲染。如果咱们没有应用 React.memo,每次点击按钮时,ChildComponent 都会从新渲染,即便其 props 没有发生变化。

自定义比拟函数

React.memo 的第二个参数是一个比拟函数,它容许咱们自定义比拟 props 的逻辑。如果咱们传递了一个比拟函数,React.memo 将应用它来决定是否从新渲染组件,而不是简略地应用浅比拟。

举例说明:

假如咱们有一个 User 组件,它承受一个 user 对象和一个 onUpdate 函数作为 props。咱们只关怀 user 对象中的 id 和 name 字段,而不关怀其余字段。此外,咱们晓得 onUpdate 函数的援用可能会常常扭转,但它的理论性能不会扭转。

在这种状况下,咱们能够应用 React.memo 的第二个参数来提供一个自定义的比拟函数:

const areEqual = (prevProps, nextProps) => {
  // 查看 user 对象中的 id 和 name 字段是否雷同
  return (
    prevProps.user.id === nextProps.user.id &&
    prevProps.user.name === nextProps.user.name
  );
};

const User = React.memo(({user, onUpdate}) => {// 组件逻辑}, areEqual);

在上述例子中,即便 onUpdate 函数的援用或 user 对象中的其余字段发生变化,只有 id 和 name 字段放弃不变,User 组件就不会从新渲染。

总之,React.memo 的第二个参数提供了弱小的自定义比拟逻辑,使咱们可能更准确地管制组件的从新渲染行为。

注意事项

传递函数给子组件

当父组件从新渲染时,如果它向子组件传递一个函数,并且这个函数是在父组件的 render 办法或函数组件的主体中定义的,那么每次父组件从新渲染时,这个函数都会被从新创立。这意味着从技术上讲,这个函数在每次渲染时都是创立一个新的函数,即便它的理论性能没有变动。

React.memo 通过浅比拟 props 来防止不必要的从新渲染。然而,因为每次都是一个新的函数实例,浅比拟会认为函数曾经扭转,从而导致子组件从新渲染。

<div ref=”memo3″ />

::: details demo 代码
<<< @/components/react/hooks/memo/FunctionInProps.jsx
:::

在上述案例中,咱们察看到以下状况:

当点击 Increment 按钮时,父组件的状态 count 发生变化,导致父组件从新渲染。因为父组件从新渲染,getText 函数也会被从新创立。

因为应用了 memo 对 ChildComponent 进行了包裹,MemoizedChildComponent 会进行浅比拟以决定是否从新渲染。因为 getText 函数在每次渲染时都是一个新的实例,浅比拟会认为该函数产生了变动,因而会触发子组件的从新渲染。

解决方案

常见的三种解决方案如下:

1、将函数移至组件内部

如果函数不依赖于组件的 props 或 state,您能够将其移至组件内部,这样它就不会在每次渲染时从新创立。

<div ref=”memo4″ />

::: details demo 代码
<<< @/components/react/hooks/memo/FunctionInProps2.jsx
:::

2、应用类组件的办法

如果您应用的是类组件,能够将函数作为类的办法,这样它的援用在从新渲染之间就会放弃不变。

<div ref=”memo5″ />

::: details demo 代码
<<< @/components/react/hooks/memo/FunctionInProps3.jsx
:::

3、应用 useCallback

如果您应用的是函数组件,能够应用 useCallback Hook 来确保函数的援用在从新渲染之间放弃不变。

<div ref=”memo6″ />

::: details demo 代码
<<< @/components/react/hooks/memo/FunctionInProps4.jsx
:::

在下面的案例中,只有当依赖列表中的值发生变化时,getText 函数才会被从新创立。

传递对象 / 数组给子组件

当父组件从新渲染时,如果它向子组件传递一个对象,并且这个对象是在父组件的 render 办法或函数组件的主体中定义的,那么每次父组件从新渲染时,这个对象都会被从新创立。这意味着从技术上讲,这个对象在每次渲染时都是创立一个新的对象,即便它的理论性能没有变动。

React.memo 通过浅比拟 props 来防止不必要的从新渲染。然而,因为每次都是一个新的对象,浅比拟会认为对象曾经扭转,从而导致子组件从新渲染。

<div ref=”memo7″ />

::: details demo 代码
<<< @/components/react/hooks/memo/ObjectInProps.jsx
:::

解决方案

常见的三种解决方案如下:

1、将对象移至组件内部

如果对象不依赖于组件的 props 或 state,您能够将其移至组件内部,这样它就不会在每次渲染时从新创立。

<div ref=”memo8″ />

::: details demo 代码
<<< @/components/react/hooks/memo/ObjectInProps1.jsx
:::

2、应用 useState 或 useRef 来存储对象

如果您的对象不常常变动,能够应用 useState 或 useRef 来存储它,这样它的援用在从新渲染之间就会放弃不变。

<div ref=”memo9″ />

::: details demo 代码
<<< @/components/react/hooks/memo/ObjectInProps2.jsx
:::

3、应用 useMemo

在某些状况下,咱们心愿组件的某些属性发生变化时,组件不会从新渲染。这时,咱们能够应用 useMemo 来返回组件的 memoized 值,从而防止组件的从新渲染。

<div ref=”memo10″ />

::: details demo 代码
<<< @/components/react/hooks/memo/ObjectInProps3.jsx
:::

在下面的代码中,userInfo 只会在组件首次渲染时创立,除非依赖列表中的值发生变化。

调用 React.memo 后大抵执行状况

graph TD
  A[React.memo] --> B[这是函数组件吗? isValidElementType 函数判断组件类型 type]
  B -->| 是 | C[用 MemoComponent 包装]
  B -->| 否 | D[抛出谬误 console.error]
  C --> E[父组件渲染]
  E --> F[父组件传递 nextProps 给子组件]
  F --> G[获取 workInProgress.memoizedProps]
  G --> H[自定义比拟函数? compare 函数有两个形参: oldProps newProps]
  H -->| 是 | I[应用自定义比拟函数比拟]
  H -->| 否 | J[浅比拟 memoizedProps 和 nextProps]
  I -->| 雷同 | K[跳过渲染并返回上次的后果]
  I -->| 不同 | L[渲染并返回]
  J -->| 雷同 | K
  J -->| 不同 | L
正文完
 0