前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 中,用到的几种浅比较方式及其比较成本科普

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

作者头像
用户6901603
发布2024-01-17 16:01:20
1340
发布2024-01-17 16:01:20
举报
文章被收录于专栏:不知非攻不知非攻

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

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

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

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

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

0、对象直接比较

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

代码语言:javascript
复制
var prevProps = {}
var nextProps = {}

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

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

代码语言:javascript
复制
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

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

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

1、Object.is

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

代码语言:javascript
复制
+0 === -0 // true
Object.is(+0, -0) // false

NaN === NaN // false
Object.is(NaN, NaN) // true

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

代码语言:javascript
复制
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;

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

代码语言:javascript
复制
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

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

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

2、shallowEqual

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

代码语言:javascript
复制
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 对象,他们最有最后一项不相同,因此比较的次数会拉满。

代码语言:javascript
复制
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}

完整代码如下

代码语言:javascript
复制
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

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

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

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

运用到实践中,就是把 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 性能优化最重要的手段非常简单:就是控制当前渲染内容的节点规模

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 这波能反杀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、对象直接比较
  • 1、Object.is
  • 2、shallowEqual
  • 3、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档