简介
上一次, 我们介绍了使用forceUpdate来重写shouldComponent生命周期(React性能优化: 剑走偏锋(一)). 这一次我们继续这个话题.
react提供了一个PureComponent, 也是重写了shouldComponentUpdate, 不过它做的是一个浅比较. 也就是说它能满足的场景非常有限. 如果项目中使用了immutable数据结构, 那么改比较基本就是失效了.
这次我们也定义个组件XPureComponent组件, 重写shouldComponentUpdate生命周期, 让它能够满足更多的场景. 不限制state和props的数据结构. 可以是任意的. 比如简单对象, 复杂对象, immutable对象, HTMLELEMENT节点等任意对象.
XPureComponent
import React from 'react';
import equals from './compare';
/**
* @template Props
* @template State
* @template Snapshot
*
* @extends {React.Component<Props, State, Snapshot>}
*/
class XPureComponent extends React.Component {
/**
* @override
*/
shouldComponentUpdate(nextProps, nextState) {
// 按照先State再Props的顺序比较,不一致则需要更新。
return !(equals(this.state, nextState) && equals(this.props, nextProps));
}
}
export default XPureComponent;
后面所有class组件就继承这个XPureComponent
class XApp extends XPureComponent {
render(){
return <div></div>
}
}
compare.js的实现
import { isImmutable, is } from 'immutable';
import { isEqual } from 'lodash';
const excludeKeys = ['__proto__', 'children'];
/**
* 判断是否为dom节点对象
* @param node
* @returns {boolean}
*/
const isDomNode = node => {
return node && node.nodeName;
};
/**
* 比较两个大对象.
* @param {Any} first
* @param {Any} second
*/
const equals = (first, second) => {
//判断是否为dom节点对象.
if(isDomNode(first) && isDomNode(second)){
return first.isSameNode(second);
}
// 先判断是否为同一个对象.
if(first === second){
return true;
}
// 两个都是
if (isImmutable(first) && isImmutable(second)) {
return is(first, second);
}
// 只有一个是
if (isImmutable(first) || isImmutable(second)) {
return false;
}
// 两个都是普通对象.
// 比较第一层.
if (
first &&
second &&
typeof first === 'object' &&
typeof second === 'object'
) {
const firstKeys = Object.keys(first);
const secondKeys = Object.keys(second);
// 先比较长度
if (firstKeys.length !== secondKeys.length) {
return false;
}
// 再比较key.
const equal = isEqual(firstKeys, secondKeys);
if (!equal) {
return false;
}
// key相同的情况下.
for (let i = 0; i < firstKeys.length; i++) {
const key = firstKeys[i];
const isExcluded = excludeKeys.findIndex(k => k === key) !== -1;
// 递归比较.
if (!isExcluded && !equals(first[key], second[key])) {
return false;
}
}
return true;
}
return isEqual(first, second);
};
export default equals;