为什么我们不默认全局使用React.memo或PureComponent?

3,057 阅读4分钟

一个困惑

我们都知道,React.memo或React.PureComponent是作为React性能优化的一种手段。当props没有变化的时候,则纯组件不会引起重渲染。理论上来说这个优化手段应该值得我们在全局使用。如果我们真的那么做了,则更进一步的,React官方就应该默认所有的组件都是“纯组件”。但事实上React官方并没有这么做,这是为什么?这个困惑在我刚开始学习React框架的时候就产生了。问了不少老前端,都没有获得好的解释,这困扰了我很久,直到最近我才有了自己的答案。

讨论的前提

如果要讨论这个问题,首先我们需要达成这样一个共识和前提:同样的一次重渲染,普通组件的性能比“纯组件”性能要更好一点。原因很简单,因为纯组件需要额外做props的浅比较。另外,React.memo需要进行额外的执行包装成一个高阶组件。

你的“纯组件”真的有性能上的优化吗?

基于以上前提,我们很容易推断:使用纯组件的收益来自于避免不必要的重渲染,而如果你的组件每一次都在重渲染,或者因为开发者原因导致无法达到纯组件的重渲染阻断条件,则性能上反而会更差!

function TestA(props: React.PropsWithoutRef<{
  count?: number,
  style?: any,
}>){
  return <div style={props.style}>{props.count || 0}</div>
}

export default React.memo(TestA)

例1:

function App() { //顶层组件
  const [count, setCount] = useState(0)

  return (
    <div className="App">
        <TestA count={count} />
        <div>{count}</div>
        <button onClick={() => setCount(count => count + 1)}>+</button>
    </div>
  )
}

例2:

function App() { //顶层组件
  const [count, setCount] = useState(0)

  return (
    <div className="App">
        <TestA style={{color: 'red'}} />
        <div>{count}</div>
        <button onClick={() => setCount(count => count + 1)}>+</button>
    </div>
  )
}

使用“纯组件”的负收益

通过以上2个简单的例子,我们很容易得出结论:“纯组件”并非可以无脑全局使用来提升性能,因为用的不对反而导致性能下降。所以React官方文档中对使用“纯组件”提高性能的措辞也是相当的谨慎。

如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

  • 对开发者有更高的要求

    按下不表。

  • 心智负担加重

    React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。

    因为“纯组件”仅对props做浅层比较,所以如果全局使用“纯组件”,则我们不得不一直留意每一个props对象是否包含复杂数据结构,以及是否可能会有深层数据单独更新导致渲染滞后的情况。另外,过分的倚重“纯组件”的重渲染规则,则可能导致意想不到的错误。

    此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。

组件性能优化的方向

  • 仅在必要时使用“纯组件”

    “在出现性能问题之前,性能优化手段往往都是无效的。”这是我个人相当信奉的一个前端开发理念。这并非是说没有必要做性能优化,这句话告诉我们做性能优化要有针对性。针对性的性能优化与无脑全局做优化其实对开发人员的技术底层理解要求更高,因为你需要去解决一个更具体的问题。所以与其全局使用“纯组件”不如全局都不使用“纯组件”。在项目开发收尾阶段或者出现性能瓶颈时,可以针对一些渲染成本较高的组件做针对性优化。

  • 高内聚,低耦合

    啊,这是程序开发永恒的指导思想!

    在工作中经常发现一些同事写一个组件动则1000多行甚至2000行,没有任何复用性、抽象化和模块化可言。不用看具体的代码你都能想象的到这样的组件一定是性能巨差而且难以维护的,因为一个状态的改变导致了整个页面的重渲染。从我个人的开发经验来说,超过200行的组件是极其罕见的,抽象得体的组件一定不会是冗长的。对状态提升的使用也需要相当谨慎,滥用状态提升往往意味着耦合度的提高和更差的复用性。当每一个组件都是高度内聚和得体的,你会发现有相当一部分组件并不需要传参,因为状态被内聚在组件内部管理。而即使不刻意使用“纯组件”,大部分组件也仅仅是在必要时才会自然发生重渲染!

往期文章:

手撸一个拖拽式表单生成低代码工具——leggo

前端如何优雅的设置一个无侵入式埋点

前端也要懂的解耦思想:从IoC容器到DI实现