函数组件中的 shouldComponentUpdate

IT技术 reactjs
2021-04-04 11:44:14

我有一个关于 React 的问题shouldComponentUpdate(未覆盖时)。我确实更喜欢纯函数组件,但我担心它每次都会更新,即使 props/state 没有改变。所以我正在考虑改用 PureComponent 类。

我的问题是:函数组件shouldComponentUpdate是否与 PureComponents具有相同的检查?还是每次都更新?

3个回答

每次父级渲染时,功能组件都会重新渲染,无论props是否更改。

但是,使用React.memo高阶组件实际上可以让功能组件获得与https://reactjs.org/docs/react-api.html#reactmemo 中shouldComponentUpdate使用的相同的检查PureComponent

您可以简单地将您的功能组件包装React.memo在导出中,如下所示。

所以

const SomeComponent = (props) => (<div>HI</div>)
export default SomeComponent

可以改为

const SomeComponent = (props) => (<div>HI</div>)
export default React.memo(SomeComponent)

例子

以下示例显示了这如何影响重新渲染

父组件只是一个普通的功能组件。它使用新的react 钩子来处理一些状态更新。

它只是有一些tick状态,仅用于提供有关我们重新渲染 prop 的频率的一些线索的目的,同时它强制每秒重新渲染父组件两次。

此外,我们有一个clicks状态,告诉我们点击按钮的频率。这就是我们送给孩子们的props。因此,如果我们使用React.memo

现在请注意,我们有两个不同类型的孩子。一个包裹着memo一个没有。Child未包装的,每次父重新渲染时都会重新渲染。MemoChild被包装的,只会在 clicks 属性改变时重新渲染。

const Parent = ( props ) => {
  // Ticks is just some state we update 2 times every second to force a parent rerender
  const [ticks, setTicks] = React.useState(0);
  setTimeout(() => setTicks(ticks + 1), 500);
  // The ref allow us to pass down the updated tick without changing the prop (and forcing a rerender)
  const tickRef = React.useRef();
  tickRef.current = ticks;

  // This is the prop children are interested in
  const [clicks, setClicks] = React.useState(0);

  return (
    <div>
      <h2>Parent Rendered at tick {tickRef.current} with clicks {clicks}.</h2>
      <button 
        onClick={() => setClicks(clicks + 1)}>
        Add extra click
      </button>
      <Child tickRef={tickRef} clicks={clicks}/>
      <MemoChild tickRef={tickRef} clicks={clicks}/>
    </div>
  );
};

const Child = ({ tickRef, clicks }) => (
  <p>Child Rendered at tick {tickRef.current} with clicks {clicks}.</p>
);

const MemoChild = React.memo(Child);

实时示例(也在CodePen 上):

对这个不错的答案的进一步解释tickRef在父组件的整个“生命周期”中都是同一个对象(这就是useRef工作原理),只有current属性发生了变化。诀窍是memo只对 props 进行浅层比较,所以它不知道它tickRef有一个不断变化的属性。
2021-05-25 11:44:14
因此可以肯定地说,深度 prop 更改也不会触发子组件中的重新渲染。这是诱使组件不重新渲染的一种方法。也很简单。
2021-05-25 11:44:14

在 React 中,函数式组件是无状态的,它们没有生命周期方法。无状态组件是编写 React 组件的一种优雅方式,我们的包中没有太多代码。但是在内部,无状态组件被包装在一个类中,当前没有应用任何优化。这意味着无状态和有状态组件在内部具有相同的代码路径(尽管我们对它们的定义不同)。

但是在未来 React 可能会优化无状态组件,正如这里所说的:

将来,我们还将能够通过避免不必要的检查和内存分配来针对这些组件进行性能优化。[更多阅读...]

shouldComponentUpdate

您可以在这里应用我们的自定义优化并避免不必要的组件重新渲染。下面解释了这种方法与不同类型组件的用法:

  • 功能性无状态组件

    如前所述,无状态组件没有生命周期方法,因此我们无法使用shouldComponentUpdate. 但是它们已经以不同的方式进行了优化,它们具有更简单和优雅的代码结构,并且比具有所有生命周期钩子的组件花费更少的字节。

  • 扩展 React.PureComponent

    React v15.3.0 开始,我们有一个新的基类调用PureComponent 来扩展PureRenderMixin内置。在幕后,这采用了当前props/状态与shouldComponentUpdate.

    也就是说,我们仍然不能依靠PureComponent类来将我们的组件优化到我们想要的水平。如果我们有Object类型(数组、日期、普通对象)的props,就会发生这种异常情况这是因为我们在比较对象时遇到了这个问题:

    const obj1 = { id: 1 };
    const obj2 = { id: 1 };
    console.log(obj1 === obj2); // prints false
    

    因此,肤浅的比较不足以确定事情是否发生了变化。但是PureComponent如果你的 props 只是字符串、数字、布尔值......而不是对象,请使用类。如果您不想实现自己的自定义优化,也可以使用它。

  • 扩展 React.Component

    考虑上面的例子;如果我们知道对象已经改变了id,那么我们可以通过比较来实现我们自己的自定义优化obj1.id === obj2.id这就是我们可以使用extend我们的普通Component基类并shouldComponentUpdate用来自己进行特定键的比较的地方。

那么可以肯定地说,使用无状态组件比使用实现了 shouldComponentUpdate 的常规组件更有效吗?
2021-05-24 11:44:14
请看这个问题
2021-05-29 11:44:14
可能值得注意的是,由于hooks,功能组件不再一定是无状态的
2021-06-09 11:44:14

另一种方法是使用useMemo仅在更新监视值时更新值:

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

在对象的情况下,在确保更新后,可以选择使用状态挂钩来缓存感兴趣变量的值。例如通过使用lodash

const [fooCached, setFooCached]: any = useState(null);

if (!_.isEqual(fooCached, foo)) {
  setFooCached(foo);
}). 

const showFoo = useMemo(() => {
    return <div>Foo name: { foo.name }</div>
}, [fooCached]);
是的,这个问题是 2 年前的,但我今天喜欢这个答案。我相信任何需要 deps 的钩子都可以做到这一点,而不仅仅是 useMemo。useEffect 具有相同的语法: useEffect(() => computeExpensiveValue(a, b), [a, b]);
2021-05-23 11:44:14