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

时间:2018-10-31 00:09:02

标签: javascript reactjs

我最近一直在玩 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在这种情况下永远不会改变,所以记忆是有意义的。

但是,道具频繁更换的情况又如何呢?
如果我添加了另一个按钮来改变状态并触发了重新渲染该怎么办,即使将CountComponent包装在 memo 中,也有意义,即使此组件是设计用来更新经常吗?

我想我的主要问题是只要所有内容都保持纯净,是否有可能不使用React Memo包装功能组件?

7 个答案:

答案 0 :(得分:11)

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

是的。如果所有组件都被React.memo包裹,您可能会导致性能下降。

在许多情况下不需要。要尝试使用对性能至关重要的组件,请先执行一些措施,添加备注,然后再次进行度量以查看是否值得增加复杂性。

React.memo的费用是多少?

一个备忘的组件将旧道具与新闻道具进行比较,以决定是否重新渲染-每个渲染周期。
在父项中的道具/状态更改后,普通组件将不在乎,而仅进行渲染。

看看在shallowEqual中调用的React updateMemoComponent实现。

何时不使用React memo

没有硬性规定。对React.memo产生负面影响的事物:

  1. 组件通常会随道具一起重新呈现,
  2. 重新渲染组件很便宜
  3. 比较功能执行起来很昂贵

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

何时补React.memo

它仅检查道具,而不从内部检查上下文更改或状态更改。如果被记忆的组件具有非基本React.memo,则children也没有用。 useMemo可以在这里补充memo,例如:

// inside React.memo component
const ctxVal = useContext(MyContext); // context change normally trigger re-render
return useMemo(() => <Child />, [customDep]) // prevent re-render of children

答案 1 :(得分:10)

您应该始终使用React.memo,因为比较组件返回的树总是比比较一对props属性要昂贵)

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

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

实际上,他们可以使版本17.0.0向前兼容,从而破坏向后兼容性,并使React.memo为默认版本,并提供某种功能来取消此行为,例如React.deepProps =)

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

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

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

P.S。如果您的组件使用children属性,则React.memo不起作用,则cuz children属性是新数组。但是最好不要为此而烦恼,并且即使这样的组件也应该包装在React.memo中,因为计算资源可以忽略不计。

答案 2 :(得分:2)

“请记住,传递给useMemo的函数在渲染过程中运行。不要执行渲染时通常不会执行的任何操作。例如,副作用属于useEffect,而不是useMemo。

您可能依赖useMemo作为性能优化,而不是语义保证。将来,React可能会选择“忘记”一些以前记忆的值,并在下一次渲染时重新计算它们,例如释放屏幕外组件的内存。编写代码,使其在不使用useMemo的情况下仍然可以正常工作-然后添加它以优化性能。 (在极少数情况下,永远不能重新计算值,则可以延迟初始化引用。)”

https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

答案 3 :(得分:2)

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

查看组件生命周期方法中的documentationshouldComponentUpdate()在渲染发生之前总是被调用 ,这意味着对组件的记忆将包括对每次更新。

考虑到这一点,记忆组件确实会对性能产生影响,这些影响的程度应通过对应用程序进行性能分析并确定在进行记忆或不进行记忆的情况下其效果更好。

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

答案 4 :(得分:2)

The same question有一个answer by markerikson on the React GitHub issue tracker。它比这里的答案更令人称赞。

  

我假设React.memoshouldComponentUpdatePureComponent同样适用于一般建议:进行比较的确花费很小,并且在某些情况下组件永远不会正确记忆(尤其是使用props.children时)。因此,不要只是将所有内容自动包装到各处。了解您的应用在生产模式下的行为,使用React的性能分析版本和DevTools分析器查看瓶颈所在,并有策略地使用这些工具来优化组件树的各个部分,这些部分实际上将从这些优化中受益。

答案 5 :(得分:0)

我认为简短的答案是: React.memo 对功能组件起作用,而 React.PureComponent 对类组件起作用。 从这种意义上讲,当您使用备忘录时,它将评估该功能组件的道具是否已更改,如果更改,则它将执行功能的返回,否则不会执行,避免重新渲染该组件。

import React, { memo } from 'react';

const Component = () => {
  debugger;
  return (
    <div>Hello World!</div>
  );
};

const MemoComponent = memo(() => {
  debugger;
  return (
    <div>Hello World!</div>
  );
});

如果您将Component用作要更新的容器的子组件,则每次父更新时,它将重新呈现(调试器将每次触发)。 另一方面,如果您使用MemoComponent,它将不会重新渲染(调试器只会在第一个渲染中触发)。

在此示例中,发生这种情况是因为功能组件没有道具,万一有道具,则只有在道具发生更改时才会发生。

答案 6 :(得分:0)

这个想法是避免使用备忘,因为备忘录可能经常更改。如博客中所述,这还包括依赖于此类数据的回调。例如

  

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

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