什么时候不应该使用 React 备忘录?

IT技术 javascript reactjs
2021-04-21 10:52:49

我最近一直在玩React 16.6.0,我喜欢React Memo的想法,但我一直找不到任何关于最适合实现它的场景的东西。

React 文档 ( https://reactjs.org/docs/react-api.html#reactmemo ) 似乎没有暗示将它放在所有功能组件上有任何影响。

因为它会进行浅层比较以确定是否需要重新渲染,所以是否会出现对性能产生负面影响的情况

像这样的情况似乎是实施的明显选择:

// NameComponent.js
import React from "react";
const NameComponent = ({ name }) => <div>{name}</div>;
export default React.memo(NameComponent);

// CountComponent.js
import React from "react";
const CountComponent = ({ count }) => <div>{count}</div>;
export default CountComponent;

// App.js
import React from "react";
import NameComponent from "./NameComponent";
import CountComponent from "./CountComponent";

class App extends Component {
  state = {
    name: "Keith",
    count: 0
  };

  handleClick = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <NameComponent name={this.state.name} />
        <CountComponent count={this.state.count} />
        <button onClick={this.handleClick}>Add Count</button>
      </div>
    );
  }
}

因为name在这种情况下永远不会改变,所以记住.

但是在 props 频繁变化的情况下呢?
如果我添加另一个按钮来改变其他状态并触发重新渲染,那么包装CountComponentmemo 中是否有意义,即使这个组件设计为经常更新?

我想我的主要问题是只要一切都保持纯净,是否存在不使用 React Memo 包装功能组件的情况?

6个回答

所有react组件都实现了该shouldComponentUpdate()方法。默认情况下(组件扩展React.Component),这始终返回 true。记忆组件(通过React.memo功能组件或扩展React.PureComponent类组件)引入的更改shouldComponentUpdate()方法的实现- 对状态和props进行浅层比较。

纵观文档上的组件生命周期方法,shouldComponentUpdate()总是调用之前渲染发生,这意味着记忆一个组件将包括在每次更新这个附加浅层比较。

考虑到这一点,记忆组件确实有性能影响,这些影响的大小应该通过分析您的应用程序并确定它是否在有或没有记忆的情况下工作得更好来确定。

为了回答您的问题,我认为何时应该或不应该记忆组件没有明确的规则,但是我认为应该应用与决定是否应该覆盖相同的原则shouldComponentUpdate():通过建议的分析工具查找性能问题并确定您是否需要优化组件。

@IvanKleshnin 有趣的一点!我对第二个语句“每个组件实例的额外渲染快照”的详细说明感兴趣
2021-05-29 10:52:49
是的,我认为 React 为我们提供的许多功能并不意味着是魔杖,而是在某些(有时是一般)情况下帮助解决问题的更多工具,我很高兴您发现我的回答很有用 @KeithBrewster
2021-06-01 10:52:49
这是一个很好的答案,我认为您完全正确,分析是确定性能缺陷程度的必要工具。
2021-06-05 10:52:49
@NickMitchell 我的意思是React.memo内部将 prop 值存储在内存中(以便能够与新的 prop 值进行比较)。如果该数据足够大,则它可以具有可测量的足迹。
2021-06-08 10:52:49
您应该提到内存影响(内存不是性能)。我不知道为什么人们认为成本React.memo只是一个额外的肤浅比较。当它实际上是“超浅比较”+“每个组件实例的额外渲染快照”时。至少对于功能组件。
2021-06-11 10:52:49

您应该始终使用React.memoLITERALLY,因为比较 Component 返回的树总是比比较一对props属性更昂贵

所以不要听任何人的,把所有的功能组件都包装在React.memo. React.memo最初是打算内置到功能组件的核心中,但由于失去了向后兼容性,默认情况下不使用。(因为它从表面上比较了对象,并且您可能正在使用组件中子对象的嵌套属性)=)

就是这样,这是 React 不自动使用备忘录的唯一原因。=)

事实上,他们可以制作 17.0.0 版本,这将打破向后兼容性,并制作React.memo默认值,并制作某种功能来取消此行为,例如React.deepProps=)

别听理论家了,伙计们 =) 规则很简单:

如果您的组件使用 DEEP COMPARING PROPS,则不要使用备忘录,否则始终使用它,比较两个对象总是比调用React.createElement()和比较两个树、创建 FiberNode 等更便宜。

理论家谈论他们自己不知道的事情,他们没有分析过react代码,他们不了解 FRP,也不了解他们的建议 =)

PS 如果您的组件使用childrenprop,React.memo将无法工作,因为childrenprop 总是会创建一个新数组。但是最好不要管这个,即使是这样的组件也应该被包裹在 中React.memo,因为计算资源可以忽略不计。

@anonym 我相信唯一的警告是以防您的道具占用大量内存。记忆意味着可能永远地将道具存储在内存中,即直到下一次重新渲染,在那里需要存储新的可能占用大量内存的道具。只有当组件被卸载时,内存才会被释放。这就是您在速度和内存之间的权衡。
2021-05-22 10:52:49
@anonym 我们有一个想法,即 React.memo 不会停止渲染其子项,但也会检查子项的 props 更改。现在React.memo阻塞了它整个树分支的渲染,这也是默认没有开启的原因之一。诀窍在于反应性具有“反应性颗粒”这样的概念;每个谷物都有依赖关系。比较依赖项,如果它们没有改变,则不会重新计算粒度。这些是 FRP 中的标准优化(React 是 FRP 实现之一)
2021-05-28 10:52:49
这个答案完全是错误的和误导性的。其实很容易想象 props 的比较会比 VDOM 的比较慢的情况。记住 VDOM 树是一个普通的 JS 数据。现在这一切都归结为 props 和 VDOM 树的大小之间的差异。如果你的组件返回一个相对较大的 VDOM 并接受占用空间相对较小的 props(常见场景),它将受益于 React.memo。但是如果你的组件返回一个相对较小的 VDOM 并且 props 相对较大......比较 VDOM 并避免额外的 props 比较会更快。
2021-06-05 10:52:49
对此有何反驳?
2021-06-08 10:52:49
@anonym 认为 React.memo 是 useEffect (Component, [depending ...]) 仅在 useEffect 的情况下,文档最初声明是表面比较数组值 =),而在 props 的情况下,这不是声明,这就是默认情况下不启用 React.memo 的原因 =) 顺便说一下,我是 React 的开发人员之一。
2021-06-13 10:52:49

是否会出现对性能产生负面影响的情况?

是的。如果所有组件都被React.memo.

在许多情况下不需要它。要尝试使用性能关键组件,请先进行一些测量,添加记忆,然后再次测量以查看增加的复杂性是否值得。

成本是React.memo多少?

记忆组件将旧组件与新闻props进行比较,以决定是否重新渲染 -每个渲染周期
在父组件中的 props/state 更改之后,普通组件不关心并且只是渲染。

看一下 ReactshallowEqual实现,它在updateMemoComponent.

什么时候不使用React memo

没有硬性规定。React.memo负面影响的事情

  1. 组件经常用props重新渲染,无论如何都改变了
  2. 重新渲染组件很便宜
  3. 比较函数执行起来很昂贵

广告 1:在这种情况下,React.memo无法阻止重新渲染,但必须进行额外的计算。
广告 2:就渲染、协调、DOM 更改和副作用成本而言,对于“简单”组件而言,增加的比较成本是不值得的。
广告3:props越多,计算越多。您还可以传入更复杂的自定义比较器

什么时候补React.memo

它只检查props,而不是从内部检查上下文更改或状态更改。React.memo如果 memoized 组件具有 non-primitive ,也是没用的childrenuseMemo可以memo在这里补充,例如:

// inside React.memo component
const ctxVal = useContext(MyContext); // context change normally trigger re-render
return useMemo(() => <Child />, [customDep]) // prevent re-render of children
重新渲染整个 HTML 组件如何比逐字比较值更便宜,可以说是每种编程语言中最快的操作之一?这在很多层面上都是错误的。虽然无意识地记忆组件确实可能需要更多的 cpu 周期,但它无法与任何重新渲染进行比较,除非您一次比较数以千计的参数,这是不太可能的。
2021-06-02 10:52:49
同样有趣的是:在链接的文章中,在将aka for 功能放在每个组件上之后,体验和测量了更差的性能PureComponentReact.memo
2021-06-13 10:52:49
你的大部分观点都是正确的,除了重新渲染的廉价性之外,我同意所有的观点。显然,在一个总是重新渲染的组件上,记忆是无用的,而且对性能不利。我只是相信 react 和 react-kind 框架的美妙之处在于虚拟 dom 的想法,它的主要目的是防止不必要的昂贵的真实 DOM 操作。所以这就是框架的重点。浅比较并不适合所有场景,但对于许多场景仍然是一个好主意。
2021-06-15 10:52:49
好吧,我的主要观点是:鉴于在大多数情况下您的组件在父级重新渲染的情况下无论如何都更改了道具,React.memo必须比普通组件做更多的工作(协调道具差异)。我个人也不喜欢过早优化——这些变化总是伴随着成本(比如增加抽象/复杂性的成本),应该是合理的。
2021-06-21 10:52:49

同样的问题有一个通过在GitHub上做出react问题跟踪markerikson答案它比这里的答案得到了更多的赞许。

我认为相同的一般建议适用于React.memo因为它为shouldComponentUpdatePureComponent:做比较,确实有小的成本,而且也场景中的分量永远不会正确memoize的(尤其是如果它使用的props.children)。所以,不要只是自动地把所有东西都包装起来。查看您的应用程序在生产模式下的行为,使用 React 的分析构建和 DevTools 分析器来查看瓶颈所在,并有策略地使用这些工具来优化组件树中真正受益于这些优化的部分。

这个想法是为了避免对可能经常变化的数据使用记忆化。正如博客中所述,这还包括依赖于此类数据的回调。例如功能,如

<Foo onClick={() => handle(visitCount)}/>

我真的很喜欢这种简单的阅读。这些例子很棒。https://dmitripavlutin.com/use-react-memo-wisely/