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

Kei*_*ter 26 javascript reactjs

我最近一直在玩React 16.6.0并且我喜欢React Memo的想法,但我一直无法找到任何关于最适合实现它的场景.React docs(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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

因为在这种情况下名称永远不会改变,所以记忆是有意义的.但道具经常变化的情况怎么样?如果我添加了另一个在状态中更改了其他内容并触发重新渲染的按钮,如果在备忘录中包装CountComponent是有意义的,即使这个组件的设计意图经常更新,该怎么办?

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

Але*_*нин 42

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

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

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

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

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

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

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

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

  • 对此有什么反驳吗? (17认同)
  • @IvanKleshnin 它不取决于道具的大小(内存) - 它取决于道具的数量。如果我不得不猜测,有很多 props(多于 vdom 节点 + 属性)的情况是极其罕见的。我不确定我是否遇到过这样合理的情况。我使用过的大多数组件都很少有 props。更不用说,组件可能有更多的自定义逻辑(钩子、效果、状态重新计算等),除非使用“React.memo”,否则可能会调用这些逻辑。 (8认同)
  • @anonym 将 React.memo 视为 useEffect (Component, [depend ...]) 仅在 useEffect 的情况下,文档最初声明数组值进行表面比较 =),而在 props 的情况下,这不是声明,这就是 React.memo 默认情况下未启用的原因 =) 顺便说一下,我是 React 的开发人员之一。 (5认同)
  • @anonym 我们有一个想法,React.memo 不会停止其子项的渲染,但也会检查子项的 props 更改。现在React.memo会阻塞其整个树枝的渲染,这也是默认情况下不启用它的原因之一。诀窍在于,反应性有一个“反应性颗粒”这样的概念。每个grain都有依赖性。比较依赖关系,如果它们没有改变,则不会重新计算grain。这些是 FRP 中的标准优化(React 是 FRP 实现之一) (2认同)
  • @anonym 我相信唯一的警告是如果你的道具内存很重。记忆化意味着可能永远将道具存储在内存中,即直到下一次重新渲染为止,其中需要存储新的可能占用大量内存的道具。只有当组件被卸载时,该内存才会被释放。这就是速度和内存之间的权衡。 (2认同)
  • 这个答案完全是错误的并且具有误导性。实际上很容易想象 props 的比较会比 VDOM 的比较慢的情况。请记住,VDOM 树是普通的 JS 数据。现在一切都归结为 props 和 VDOM 树大小之间的差异。如果您的组件返回相对较大的 VDOM 并接受相对较小占用空间的 props(常见场景),它将受益于 React.memo。然而,如果你的组件返回一个相对较小的 VDOM 并且 props 相对较大......比较 VDOM 会更快并避免额外的 props 比较。 (2认同)
  • 有人知道有一个 babel 插件可以自动用 React.memo 包装所有功能组件吗? (2认同)
  • @IvanKleshnin 你的回答听起来很合理,但事实并非如此。原因: - `React.memo` 有一个比较器(第二个参数)。这意味着它总是比 vdom 比较更快。当然,它需要额外的代码。- 如果有很多 props,渲染函数中的计算应该很复杂(可能你会说有 useMemo,但大多数时候,`useMemo` 用于优化状态变化)。- 在vdom比较之前,还需要创建vdom,这有额外的成本。- 最重要的是,`React.memo`可以避免后续的计算。 (2认同)

for*_*d04 19

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

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

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

成本是React.memo多少?

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

看一下 ReactshallowEqual实现,它在updateMemoComponent.

什么时候不使用React memo

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

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

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

什么时候补React.memo

它只检查道具,而不是从内部检查上下文更改或状态更改。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
Run Code Online (Sandbox Code Playgroud)

  • 重新渲染整个 HTML 组件怎么可能比逐字比较值更便宜,这可以说是每种编程语言中最快的操作之一?这在很多层面上都是错误的。虽然无意识地记住组件确实可能会占用更多的 cpu 周期,但它无法与任何重新渲染进行比较,除非您确实一次比较数千个参数,而这是不可能的。 (5认同)
  • 好吧,我的主要观点是:鉴于您的组件在大多数情况下已经更改了 props,在父级重新渲染的情况下,“React.memo”必须比普通组件做更多的工作(协调*和* props diffing)。我个人也不喜欢[过早优化](https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578?gi=5f251596a04d#0ae8) - 这些改变总是伴随着成本(就像增加抽象/复杂性的成本)并且应该是合理的。 (4认同)
  • 同样有趣的是:在链接的文章中,[更差的性能](https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578?gi=5f251596a04d#f11e)在放置后已经经历和测量“PureComponent”又名“React.memo”,用于每个组件上的函数。 (2认同)
  • 您的大部分观点都是正确的,我同意除了重新渲染的成本之外的所有内容。显然,在总是重新渲染的组件上,记忆是没有用的,并且对性能不利。我只是相信 React 和 React-kind 框架的美妙之处在于虚拟 DOM 的想法,其主要目的是防止不必要的昂贵的真实 DOM 操作。这就是框架的重点。浅层比较并不适合所有场景,但对于许多场景来说仍然是一个好主意。 (2认同)

and*_*par 8

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

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


Nto*_*ane 5

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

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

考虑到这一点,对组件进行记忆确实具有性能影响,并且应通过对应用程序进行性能分析并确定其在使用或不使用记忆的情况下是否更好来确定这些影响的程度。

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

  • 您应该提到内存影响(内存不是性能)。我不知道为什么人们认为 `React.memo` 的成本只是一个非常肤浅的比较。当它实际上是“超浅比较”+“每个组件实例的额外渲染快照”时。至少对于功能组件。 (4认同)
  • @IvanKleshnin 有趣的一点!我对第二个语句“每个组件实例的额外渲染快照”的详细阐述感兴趣 (3认同)
  • @NickMitchell我的意思是“React.memo”内部将道具值存储在内存中(以便能够与新的道具值进行比较)。如果该数据足够大,那么它的足迹就可以测量。 (3认同)
  • 这是一个很好的答案,我认为您完全正确,分析是确定性能缺陷程度的必要工具。 (2认同)

Zee*_*ena 5

The idea is to avoid using memoization, for data which has the possibility of changing very often. As stated in the blog, this also includes callbacks which depends on such types of data. For example functions such as

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

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