跳过渲染和React中的记忆

发布于:2021-02-04 11:15:20

0

169

0

React 渲染 组件

在许多情况下,React组件将在不必重新渲染时重新渲染。

如果渲染的结果与组件上一次渲染的结果完全相同,则最好完全跳过该渲染(协调)步骤。

基于类的组件

shouldComponentUpdate  

在类组件中,方法shouldComponentUpdate允许这样做。

它是在render()之前调用的生命周期方法。该方法返回一个布尔值。如果render()可以跳过,这个布尔值告诉React。

true时,render()将像平常一样执行。

false时,告诉React它可以跳过执行render()

用下一个道具和下一个状态调用shouldComponentUpdate()。这允许复杂的逻辑,将当前的属性/状态与以前的属性/状态进行比较,以确定输出是否不同,因此组件应该更新。

默认情况下,shouldComponentUpdate()返回true。

完全不指定此方法。

shouldComponentUpdate(nextProps, nextState) {
return true
}

不要依赖于此来完全阻止渲染。它现在可能工作,但它可能会导致错误,并可能在未来发生变化。相反,将其视为一个反应提示,告诉它“您可以安全地跳过渲染,结果将与上一个结果相同”。

shouldComponentUpdate中的逻辑很快就会变得非常复杂,并且容易出错。

在您知道之前,该方法看起来会是这样的。

shouldComponentUpdate(nextProps, nextState) {
 const propsComparison = this.props.a !== nextProps.a && this.props.b !== nextProps.b && this.props.c !== nextProps.c && this.props.d !== nextProps.d
 const stateComparison = this.state.one !== nextState.one && this.state.two !== nextState.two && this.state.three !== nextState.three
return propsComparison && stateComparison
}

我只是想看看有没有什么道具或者状态改变了,为什么这么难?

React.PureComponent  

React.PureComponent就是这样!

PureComponent执行道具和状态的浅表比较(通过使用Object.is)。

这会减少跳过必要更新的可能性(例如,添加新道具时)。

除非您确信需要自定义道具,否则最好选择PureComponent

这意味着这两个片段是等价的。

class Driver extends React.Component {
 shouldComponentUpdate() {
   // a shallow comparison of all the props and state
 }
 render() {{this.props.name};
 }
}

class Driver extends React.PureComponent {   render() {{this.props.name};   } }

功能部件

当试图将同样的优化应用于函数组件而不是基于类的组件时,问题就出现了。函数组件不能真正跳过渲染步骤。函数组件(实际上只是一个函数)要么执行要么不执行

这就是备忘录有帮助的地方。

备忘录基本上是一种技术,用于以后记住一些东西。

React不能只记住数据片段,它可以记住整个组件。

React.memo  

React.memo这样做!

前两个示例是针对基于类的组件的,React.memo是针对函数组件的。

不像在基于类的组件中那样跳过渲染步骤,React.memo将重用上次渲染的结果,而不是计算新结果。

// the function component
const Driver = function(props) {
 return{props.name};
};
// exporting the memoized function component
export default React.memo(Driver);

  • 带有道具的记忆驱动程序组件的初始呈现{ name: "Charles Leclerc" }

    • 函数组件render <p>Charles Leclerc</p>。

  • 道具更改为{ name: "Daniel Ricciardo" }

    • 组件渲染 <p>Daniel Ricciardo</p>

  • 触发驱动程序更新的其他更改组件

    • React.memo看到道具没有更改。

    • React使用前面的结果而不是计算渲染结果:<p>Daniel Ricciardo</p>

默认情况下,React.memoReact.PureComponent相当,因为它对所有道具进行了浅层比较(使用对象.is如果您想获得更多的控制权并负责比较,React.memo接受第二个参数,即比较函数。这使得它在基于类的组件中与shouldComponentUpdate相当。

比较函数还返回一个布尔值。

该布尔值告诉React它是否应该使用组件的前一个结果,而不是计算新的结果。

false时,函数组件将像平常一样执行。

true时,函数组件将不执行,并且将使用前面的结果。

小心!这与shouldComponentUpdate相反!

使用上一个道具和下一个道具调用比较函数。这允许复杂的逻辑,将当前道具与以前的道具进行比较,以确定输出是否不同,因此应使用组件的记忆结果/备忘录。

// the function component
const Driver = function(props) {
 return{props.name};
};
// the custom comparison function
const comparisonFn = function(prevProps, nextProps) {
 return prevProps.name === nextProps.name;
};
// exporting the memoized function component
export default React.memo(Driver, comparisonFn);

要使用基于类的组件扩展并行程序:

除非您确信需要自定义比较函数,否则最好使用默认行为。

示例

在这个演示中,有一个顶级组件有两个状态,一个是count和一个是unusedCount。顾名思义,它仍将被闲置。

您可以通过按钮增加countunusedCount。

顶部组件有4个子组件,所有子组件都将显示count以及该子组件渲染的次数。

只有在更新count时,具有上述优化之一的组件才会渲染。当更新unusedCount时,其他的也会渲染。

React.memoReact.useMemo

React.memo是一个高阶组件,因为它接受一个组件并返回新的/记忆的组件。

React.useMemo是一个hook(它是一个函数)。它接受一个函数并返回所传递函数的记忆返回值。

React.useMemo  

const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);

React.useMemo接受函数作为第一个参数。此函数返回的值是React.useMemo将返回的值。只有在必要时才会重新计算。React.useMemo将返回已记忆/已记忆的值(如果没有)。

告诉React.useMemo是否应该通过第二个参数(数组)重新计算结果。传递的函数返回的值只有在依赖项数组中的某些内容发生更改时才会再次计算。不传递任何内容将导致每次呈现组件时都计算该值(并导致函数运行)。

传递的函数中使用的每个值都应包含在依赖项数组中。

这将防止许多意外行为。

React团队创建了一个ESLint包,eslint-plugin-react-hooks,用于在违反hook规则时发出警告。正在完成的依赖关系数组由该包中名为exhaustive-deps的规则检查。

示例

import React from 'react';

function calculatePodiums(name) {
 // very expensive calculation
 return numResult;
}

const Driver = function(props) {
 const numOfPodiums = React.useMemo(() => calculatePodiums(props.name), [
   props.name
 ]);
 return (My name is: {props.name}I drive for: {props.team}I have been on the podium {numOfPodiums} times);
};

  • 使用props的驱动程序组件的初始呈现{ name: "Kimi Räikkönen", team: "Ferrari" }

    • 函数组件计算numOfPodiums并使用该计算的结果进行呈现。

  • props更改为{ name: "Kimi Räikkönen", team: "Alfa Romeo Racing" }

    • React.useMemo看不到依赖项数组中的任何内容已更改,并且不会重新计算numOfPodiums

    • 使用numOfPodiums的memo/membered值。

  • 道具再次更改为{ name: "Antonio Giovinazzi", team: "Alfa Romeo Racing" }

    • React.useMemo 看到依赖项数组中发生了一些变化并计算 numOfPodiums

    • 并使用新计算的值。

React.useCallback

这是特定React.useMemo用法的快捷方式。

React.useMemo返回已记忆的值.

React.useCallback返回已记忆的函数.

但值完全可以是函数!

这意味着这两个片段是等价。

const memoizedFunction = React.useMemo(function() {
 return function doTheThing(a, b) {
   // do the thing
 }
}
}, [a, b])

这将记忆第一个参数(函数)返回的值,该值是一个名为doTheThing。

const memoizedFunction = React.useCallback(function doTheThing(a, b) {
   // do the thing
 }
}, [a, b])

这将记忆第一个参数,这是一个名为doTheThing的函数。

React.useMemo一样,第二个参数是一个依赖项数组。

函数React.useCallback仅当该数组中的某些内容发生更改时才会返回更改。