函数组件中的shouldComponentUpdate

时间:2016-12-01 11:47:56

标签: reactjs

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

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

3 个答案:

答案 0 :(得分:9)

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

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

  

将来,我们还可以进行性能优化   通过避免不必要的检查和记忆来特定于这些组件   分配。 [ More reading... ]

shouldComponentUpdate

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

  • 功能无状态组件

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

  • 扩展React.PureComponent

    React v15.3.0 ,我们有一个名为 PureComponent 的新基类 通过内置的 PureRenderMixin 进行扩展。在引擎盖下,它使用当前道具/状态与shouldComponentUpdate内的下一个道具/状态的浅层比较。

    尽管如此,我们仍然不能依赖PureComponent类来将我们的组件优化到我们想要的水平。如果我们拥有Object类型的道具(数组,日期,普通对象),就会出现这种异常情况。这是因为我们在比较对象时遇到了这个问题:

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

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

  • 扩展React.Component

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

答案 1 :(得分:5)

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

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

中使用的相同的PureComponent检查

您只需在导出时将功能组件包装在React.memo中,如此处所示。

所以

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

可以代替

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

示例

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

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

它只有一个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);

您可以在此处https://codepen.io/anon/pen/ywJxzV

查看示例

答案 2 :(得分:0)

另一种方法是仅在观看值更新时使用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]);