函数组件中的shouldComponentUpdate

Moe*_*ler 24 reactjs

我有一个关于React的问题shouldComponentUpdate(当没有被覆盖时).我更喜欢纯粹的功能组件,但我担心它每次都会更新,即使道具/状态没有改变.所以我正在考虑使用PureComponent类.

我的问题是:功能组件shouldComponentUpdate是否与PureComponents 具有相同的检查?或者它每次更新?

the*_*eff 16

无论道具是否更改,功能组件都会在父母每次渲染时重新渲染。

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

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

所以

const SomeComponent = (props) => (<div>HI</div>)
export default SomeComponent
Run Code Online (Sandbox Code Playgroud)

可能是

const SomeComponent = (props) => (<div>HI</div>)
export default React.memo(SomeComponent)
Run Code Online (Sandbox Code Playgroud)

以下示例显示了这如何影响转售

父组件只是常规功能组件。它正在使用新的react挂钩来处理一些状态更新。

它只是具有某种tick状态,该状态仅用于提供一些线索,说明我们重新渲染道具的频率,而它每秒强制两次渲染父组件。

此外,我们有一个clicks状态,可以告诉我们单击按钮的频率。这就是我们送给孩子们的道具。因此,如果我们使用点击次数,则仅应重新显示点击次数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);
Run Code Online (Sandbox Code Playgroud)

您可以在这里查看示例https://codepen.io/anon/pen/ywJxzV

  • *对这个好答案的进一步解释*: `tickRef` 在父组件的整个“生命周期”中都是同一个对象(这就是 `useRef` 的工作原理),并且只有 `current` 属性被更改。诀窍是,“memo”只对 props 进行浅层比较,因此它不知道“tickRef”有一个不断变化的属性。 (5认同)
  • 因此可以肯定地说,深度 prop 更改也不会触发子组件中的重新渲染。这是欺骗组件不重新渲染的一种方法。也很简单。 (3认同)

Yad*_*ran 13

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

但是在未来,React可以优化无状态组件,如下所述:

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

shouldComponentUpdate

这是我们可以应用自定义优化并避免不必要的重新呈现组件的地方.下面介绍了使用不同类型组件的此方法:

  • 功能无状态组件

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

  • 扩展React.PureComponent

    React v15.3.0开始,我们有一个新的基类PureComponent ,使用PureRenderMixin内置函数进行扩展.在引擎盖下,这采用了当前道具/状态与a中的下一个道具/状态的浅层比较shouldComponentUpdate.

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

    const obj1 = { id: 1 };
    const obj2 = { id: 1 };
    console.log(obj1 === obj2); // prints false
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 扩展React.Component

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

  • 值得注意的是,由于 [hooks](https://reactjs.org/docs/hooks-intro.html),功能组件不再一定是无状态的。 (7认同)

kru*_*kat 13

具体道具对比

这里已经有很多很好的答案。我只是想添加一个示例,您想要专门比较一个道具(而不是所有道具)并决定基于此重新渲染。

例子

下面是一个我只想在字符串属性更改Header时重新渲染的组件。heading

import React from 'react'

const Header = ({ heading }) => {
  console.log('Header')
  return (
    <h2 className='bg-gray-300 text-gray-800 text-center py-6 px-8 rounded-md text-xl font-extrabold'>
      {heading}
    </h2>
  )
}


const areEqual = (prevProps, nextProps) => {
  if (prevProps.heading === nextProps.heading) {
    return true                                    // donot re-render
  }
  return false                                     // will re-render
}


export default React.memo(Header, areEqual)
Run Code Online (Sandbox Code Playgroud)

关于返回值的关键areEqual是:

  • return true=> 不重新渲染
  • return false=> 将重新渲染

我从这里的官方文档中得到了这个

  • 如果你还在关注这个。你的回答救了我!我的团队在这个问题上陷入了很长一段时间。这正是我们想要的解决方案!万分感谢! (2认同)

ego*_*ego 9

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

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Run Code Online (Sandbox Code Playgroud)

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

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

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

const showFoo = useMemo(() => {
    return <div>Foo name: { foo.name }</div>
}, [fooCached]);
Run Code Online (Sandbox Code Playgroud)

  • 是的,这个问题是两年前的事了,但我喜欢今天的答案。我相信任何需要 deps 的钩子都可以做到这一点,而不仅仅是 useMemo。useEffect 具有相同的语法:`useEffect(() =&gt;computeExpectiveValue(a, b), [a, b]);` (4认同)