shouldComponentUpdate应该是提到的优化方法里面提到最多的,使用的也是最多的,如果返回false,就会阻止下面组件的渲染,反之,返回true,就会进行接下来的渲染,默认返回的事true。但是有个问题,shouldComponentUpdate进行的是浅比较,为什么不进行深比较,因为伤不起,深比较的耗费性能可能比每次render的效率还要低。
class Nest extends React.Component {
shouldComponentUpdate = (nextProps, nextState) => {
return (
!shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState)
)
}
render() {
console.log('inner')
return Nest
}
}
为什么进行的事浅比较,我们从源码方面来看一下:
const hasOwn = Object.prototype.hasOwnProperty
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export default 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
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
首先是Object.is的Polyfill,为什么要用这个来比较而不是 == 或者 === 呢?
使用 == 会 出现以下bug:
0 == ' ' // true
null == undefined // true
[1] == true // true
使用 === 会出现以下bug:
+0 === -0 // true,
NaN === NaN // false,
Object.is修复了=== 这两种判断不符合预期的情况,并null,undefined,number,string,boolean做出非常精确的比较,以下几种情况都会返回true。
当对比的类型为Object的时候并且key的长度相等的时候,浅比较也仅仅是用Object.is()对Object的value做了一个基本数据类型的比较,所以如果key里面是对象的话,就不能进行比较。
const hasOwn = Object.prototype.hasOwnProperty
// 下面就是进行浅比较了, 有不了解的可以提issue, 可以写一篇对比的文章。
function is(x, y) {
// === 严格判断适用于对象和原始类型。但是有个例外,就是NaN。
if (x === y) {
//这个是个例外,为了针对0的不同,譬如 -0 === 0 => true
// (1 / x) === (1 / y)这个就比较有意思,可以区分正负0, 1 / 0 => Infinity, 1 / -0 => -Infinity
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
// 这个就是针对上面的NaN的情况
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
if (is(objA, objB)) return true //这个就是实行了Object.is的功能。实行的是SameValue策略。
// is方法之后,我们认为他不相等。不相等的情况就是排除了(+-0, NaN)的情况以及可以证明:
// 原始类型而言: 两个不是同类型或者两个同类型,值不同。
// 对象类型而言: 两个对象的引用不同。
//下面这个就是,如果objA和objB其中有个不是对象或者有一个是null, 那就认为不相等。
//不是对象,或者是null.我们可以根据上面的排除来猜想是哪些情况:
//有个不是对象类型或者有个是null,那么我们就直接返回,认为他不同。其主要目的是为了确保两个都是对象,并且不是null。
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
//如果上面没有返回,那么接下来的objA和objB都是对象了。
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
//两个对象不同,有可能是引用不同,但是里面的内容却是相同的。例如:{a: 'a'} ==~ {a: 'a'}
//所以先简单粗暴的判断一级的keys是不是相同的长度。,不是那就肯定不相等,就返回false。
if (keysA.length !== keysB.length) return false
//下面就是判断相同长度的key了
// 可以发现,遍历的是objA的keysA。
//首先判断objB是否包含objA的key,没有就返回false。注意这个是采用的hasOwnPrperty来判断,可以应付大部分的情况。
//如果objA的key也在ObjB的key里,那就继续判断key对应的value,采用is来对比。哦,可以发现,只会对比到第以及。
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
个人觉得此优化手段适用于数据变化不太频繁,如果只有一个或者展示类的组件可以使用,因为在父组件进行重新render时候,可以有效避免利用PureComponent的浅比较避免组件的渲染,不可以有每次都会变动的值,因为这样你的 PureComponent 和 Component 其实没两样。但是一下几个小tip,可能避免组件重新render,其实这样的点有很多,只是把我遇到的全部列举出来。
CustomList 在 data 值为 null 或 undefined 时,仍不会发生奔溃,但如果你这么做,你会发现即使每次传入的 props 都是[],仍然会发生 render ,原因在于[] !== [],他们的 reference 并不相同。解决办法: 设置defaultProps 默认值。
this.setState({number: this.state.number + 1}) } />
这样 每次点击时,都会传入一个新的function,又因为reference不同,导致每次都会渲染。
解决办法: 将内连的function,进行预定义,拆出来定义。
1 // constructor里面绑定this;
2
3 this.handleClick.bind() } />
方式一: 构造函数每一次渲染的时候只会执行一遍; 性能最好;
方式二: 在每次render()
的时候都会重新执行一遍函数;
方式三: 每一次render()
的时候,都会生成一个新的箭头函数,即使两个箭头函数的内容是一样的,因为react
判断是否需要进行render
是浅层比较,简单来说就是通过===
来判断的,如果state
或者prop
的类型是字符串或者数字,只要值相同,那么浅层比较就会认为其相同
当你使用 PureComponent 時,如果 props 或 state 要变动,可以尝试使用 Immutable.js 来处理,避免有改了却沒重新 render 的情况发生。
Immutable提供一直简单快捷的方式以判断对象是否变更,对于React组件更新和重新渲染性能可以有较大帮助。immutable可以几乎在全家桶中结合,打算专门写一篇介绍。
import { is } from 'immutable';
shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
const props = this.props || {}, state = this.state || {};
if (Object.keys(props).length !== Object.keys(nextProps).length ||
Object.keys(state).length !== Object.keys(nextState).length) {
return true;
}
for (const key in nextProps) {
if (!is(props[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (state[key] !== nextState[key] || !is(state[key], nextState[key])) {
return true;
}
}
return false;
}
4.单组件中拆分组件,尽量避免嵌套方法,一串传参下去,导致一参数改变,一串函数都会重新render;
5.多组件同样也应该避免从父级获取store数据传给子组件,会导致父组件中的子组件某个数据改变,导致父组件中所有子组件都会改变,全部重新render;