React 中,用到的几种浅比较方式及其比较成本科普

React 知命境第 39 篇,原创第 150 篇

开发中的绝大多数时候,我们并不需要关注 React 项目的性能问题。虽然我们在前面几个章节中,也花了几篇文章来分析如何优化 React 的性能体验,但是这些知识点在开发过程中能用到的机会其实比较少。面试的时候用得比较多。

但是,当你的项目遇到性能瓶颈,如何优化性能就变得非常重要。当然,我们前面几篇文章已经把性能优化的方式和方法说得非常清晰了,大家可以回顾一下。这篇文章我们要分享的重点是,当我采用不同的方式优化之后,代码逻辑执行所要付出的代价到底如何。

例如,当我们得知 React 的 DIFF 是全量比较的时候,可能第一个反应就是觉得他性能差。但是具体性能差到什么程度呢?有没有一个具体的数据来支撑?不确定,这只是一种主观感受。优化之后的性能到底强不强呢,也不敢肯定。

因此,这篇文章主要给大家介绍几种 react 在 diff 过程中用到的比较方式,以及当这几种方式大量执行时,执行所要花费的时间。

0

对象直接比较

又称为全等比较,这是一种成本最低的比较方式。在 React 中,state 与 props 的比较都会用到这样的方式。

var prevProps = {}
var nextProps = {}

if (prevProps === nextProps) {
  ...
}

那么,这种比较方式的成本有多低呢?我们来写一个循环简单验证一下。分别看看比较一万次需要多长时间。

var markTime = performance.now()
var prev = {}
var next = {}
for(var i = 0; i <= 10000; i++) {
  if (prev === next) {
    console.log('相等')
  }
}
var endTime = performance.now()
console.log(endTime - markTime)

执行结果会有小范围波动,展示出来的结果都是取的多次执行的平均值,或者出现次数最多的执行结果。比如本案例执行,用时最短的是 0.3 ms,用时最长的是 0.8 ms

可以看到,对比一万次,用时约  0.6ms

React 中,用到的几种浅比较方式及其比较成本科普

对比一百万次,用时约 6.4ms

React 中,用到的几种浅比较方式及其比较成本科普

通常情况下,我们项目的规模应该很难超过一万次,控制得好一点,一般都在 1000 次以内。多一点也应该在 5000 次以内,5000 次用这种方式的对比只需要 0.3ms 左右。

1

Object.is

Object.is 是一种与全等比较相似但不同的比较方式,他们的区别就在于处理带符号的 0 和 NaN 时结果不一样。

+0 === -0 // true
Object.is(+0-0// false

NaN === NaN // false
Object.is(NaNNaN// true

React 源码里为 Object.is 做了兼容处理,因此多了一点判断,所以他的性能上会比全等要差一些

function is(x, y{
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs =
  typeof Object.is === 'function' ? Object.is : is;

那么差多少呢?我们先写一个逻辑来看一下执行一万次比较需要多久

function is(x, y{
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs =
  typeof Object.is === 'function' ? Object.is : is;


var markTime = performance.now()
var prev = {}
var next = {}
for(var i = 0; i <= 10000; i++) {
  if (objectIs(prev, next)) {
    console.log('相等')
  }
}
var endTime = performance.now()
console.log(endTime - markTime)

执行结果如下,大概是 0.8ms

React 中,用到的几种浅比较方式及其比较成本科普

执行一百万次,用时约 11.4ms

React 中,用到的几种浅比较方式及其比较成本科普

那么我们的项目规模在 5000 次比较以内的话,用时估计在 0.4ms 左右,比全等比较多用了 0.1ms

2

shallowEqual

这种浅比较的成本就稍微大一些,例如,当我们对子组件使用了 memo 包裹之后,那么在 diff 过程中,对于 props 的比较方式就会转变成这样方式,他们会遍历判断 props 第一层每一项子属性是否相等。

function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

首先,这种比较方式在 React 中出现的次数非常的少,只有我们手动新增了 memo 之后才会进行这种比较,因此,我们测试的时候,先以 1000 次为例看看结果

我们定义两个数量稍微多一点的 props 对象,他们最有最后一项不相同,因此比较的次数会拉满。

var prev = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1}
var next = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 2}

完整代码如下

function is(x, y{
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs =
  typeof Object.is === 'function' ? Object.is : is;


function shallowEqual(objA, objB{
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !Object.hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

var markTime = performance.now()
var prev = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1}
var next = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 2}

for(var i = 0; i <= 1000; i++) {
  if (shallowEqual(prev, next)) {
    console.log('相等')
  }
}
var endTime = performance.now()
console.log(endTime - markTime)

1000 次比较结果耗时如下,约为 1.4ms

React 中,用到的几种浅比较方式及其比较成本科普

5000 次比较结果耗时如下,约为 3.6ms

React 中,用到的几种浅比较方式及其比较成本科普

10000 次比较结果耗时如下,约为 6.6 ms

React 中,用到的几种浅比较方式及其比较成本科普

这里我们可以做一个简单的调整,让对比耗时直接少一半。那就是把唯一的变化量,写到前面来,如图所示,耗时只用了 3.1ms

React 中,用到的几种浅比较方式及其比较成本科普

运用到实践中,就是把 props 中的变量属性,尽量写在前面,能够大幅度提高对比性能。

3

总结

次数 全等 is shallow
五千 0.3 0.4 3.6
一万 0.6 0.8 6.6
百万 6.4 11.4 162

因此我们从测试结果中看到,全量 diff 并不可怕,如果你对性能优化的理解非常到位,那么能你的项目中,全量 diff 所花费的时间只有 0.几ms理论的极限性能就是只在你更新的组件里对比出差异,执行 re-render

当然,由于对于 React 内部机制的理解程度不同,会导致一些差异,例如有些同学的项目中,会执行过多的冗余 re-render。从而导致在大型项目中性能体验可能出现问题。那么这种情况下,也不用担心,有一种超级笨办法,那就是在项目中,结合我们刚才在 shallowEqual 中提高的优化方案,无脑使用 useCallbackmemo,你的项目性能就能得到明显的提高,当然,这个方案不够优雅但是管用。

可以看出,React 性能优化最重要的手段非常简单:就是控制当前渲染内容的节点规模

除此之外,可以翻阅我们前面对于性能优化的具体措施学习更加细节的优化手段。

「React 知命境」 是一本从知识体系顶层出发,理论结合实践,通俗易懂,覆盖面广的精品小册,点击下方标签可阅读其他文章。欢迎关注我的公众号,我会持续更新。购买 React 哲学,或者赞赏本文 30 元,可进入 React 付费讨论群,学习氛围良好,学习进度加倍。赞赏之后也能看到 React 哲学的全部内容


原文始发于微信公众号(这波能反杀):React 中,用到的几种浅比较方式及其比较成本科普

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/240158.html

(0)
葫芦侠五楼的头像葫芦侠五楼

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!