共计 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
状态的变动),因为传递给 MemoizedChildComponent
的 text
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