“晚上下班回家,时间充裕的情况下会看react的源码,虽然感觉看的时候就像屎山。今天就来讲解一下PureComponent和Component究竟有什么区别,其实就是带领大家了解一下相关的源码涉及到什么。”
1. 区别
在讲解源码之前,首先我们需要会用,然后在熟悉之后才来说了解源码。 首先讲一下它们之间的区别。当使用Component时,父组件的state或者props更新时,无论子组件的state或者props是否更新,都会导致子组件的更新,因此会造成render渲染,浪费许多性能。这个时候就出现了PureComponent的使用,它其实就是在shouldComponentUpdate进行了浅层比较,减少了不必要的render渲染。
可能你会很好奇为什么是浅层比较而不是深层比较?因为是对性能考虑,在js中对引用类型(浅、深层次引用)的比较其实是需要通过递归比较的。因此如果在组件生命周期中采用递归比较state、props对象,这样会产生较大的性能消耗,所以采用了折中处理,即浅比较。
2. 源码解析
接下来就进入到激动人心的源码解析环节,在这里我不会挨着介绍源码,会挑重点来讲解,如果你对React源码很感兴趣的话那么可以自己研究。
首先找到PureComponent的源码其中有一行很关键的代码pureComponentPrototype.isPureReactComponent = true;在原型上添加isPureReactComponent字段且设置值为true来区分是Component还是PureComponent;
找到ReactFiberClassComponent.new.js文件下的checkShouldComponentUpdate函数下的如下代码:
1 2 3 4 5 6 7 8
| // 此时如果你是PureComponent组件时在其原型链上找到isPureReactComponent为true if (ctor.prototype && ctor.prototype.isPureReactComponent) { return ( // 这个时候就会进入到shallowEqual浅层比较,这个函数就是PureComponent最精髓的一点 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) ); } return true; // 默认是返回true
|
接着我们找到shallowEqual函数,得到代码如下所示,此时我们会在源码中看到is函数,然后你可以跳到下一步中看is函数的解析然后再回过头来看shallowEqual函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| // 借用原型链上正真的hasOwnProperty方法 const hasOwnProperty = Object.prototype.hasOwnProperty; function shallowEqual(objA, objB) { /** * 我们在看了is函数的封装之后可以知道is函数可以对基本数据进行精确的比较, * 但是有一点会出现问题:对象引用时,如果只是改变对象中的某个属性值而引用指向不变时, * is会判断为true,因为它们是指向相同的地址,就像: * let o1 = {a: 1}; let o2 = o1; o1.a = 'hello'; * o1和o2中a的值都变为hello,它们之间的引用地址都是指向同一个,即o1 === o2为true */ if (is(objA, objB)) { return true; } // objA或者objB不为对象时返回false if ( typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null ) { return false; } // objA和objB都为对象时对key的长度进行比较,如果它们的长度不相等时返回false const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } /** * key长度相等时,去循环比较objB对象中是否有objA的key值,如果有 * 那么就会去比较对象之间的value值是否相等。 */ for (let i = 0; i < keysA.length; i++) { if ( !hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]]) ) { return false; } }
return true; }
|
is函数代码如下所示,在源码中我给了相关的解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function is(x, y) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) }
// 对于上面的源码我们可以进行一个拆解(其实就是处理基本类型) function is(x, y) { if (x === y) { /** * 考虑到-0和+0的情况,1/0为Infinity,1/-0为-Infinity * Infinity === -Infinity为false,即1/0 === 1/-0为false */ return x !== 0 || 1 / x === 1 / y; } else { /** * 还有谁不等于本身呢?答案肯定是NaN,此情况是处理NaN === NaN的情况 * 因此当x为NaN,并且y为NaN时下面会返回true */ return x !== x && y !== y; } }
|
对于上面的源码我就大致介绍到这里,其实还是很简单的。在这里我们需要注意的是:在上面两个对象进行浅层比较时也只是使用Object.is将对象的value进行基本数据的对比;如果对象是深层次的,比如:let obj = {a: {b: ‘hello world’}};那么我们的is函数的判断可能会出现不符合你预想的情况。
深层次比较可能会不准确,比如obj1 = {a: {b: 'hello world'}}
和obj2 = {a: {b: 'hello world'}}
,它们虽然外表看起来一样的,但是由于是引用类型它们比较的是地址,此时obj1和obj2引用不是同一个,因此is函数判断obj1和obj2时会返回false,即!is(objA[keysA[i]], objB[keysA[i]])
会返回true,最终shallowEqual函数返回false,checkShouldComponentUpdate函数返回true,导致shouldComponentUpdate函数会返回true,导致组件出现无意义的更新。