本文源码是2018年9月12日拉取的React仓库master分支上的代码html
React.PureComponent 官方文档:reactjs.org/docs/react-…react
React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.git
React.PureComponent 和 React.Component 几乎相同,区别在于 React.PureComponent 会 浅比较 props、state是否发生变化从而决定是否更新组件(这里的浅比较在后面的源码分析中会提到)github
使用 React.PureComponent 也是React应用优化的一种方式,固然也能使用 React.Component 定义shouldComponentUpdate
生命周期函数来实现同样的功能,可是直接使用 React.PureComponent 能更加直观和简便api
看一个简单的例子:数据结构
使用React.Component架构
class CounterButton extends React.Component {
state = {
count: 1
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
);
}
}
复制代码
使用React.PureComponentapp
class CounterButton extends React.PureComponent {
state = {
count: 1
}
render() {
return (
<button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
);
}
}
复制代码
上面两段代码都能避免没必要要的组件更新,优化性能函数
ReactBaseClasses.jsoop
const emptyObject = {};
/** * Base class helpers for the updating state of a component. */
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
/** * Convenience component with default shallow equality check for sCU. */
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
export {Component, PureComponent};
复制代码
从源码来看,Component 和 PureComponent 基本同样,惟一区别在于 PureComponent 定义了 isPureReactComponent
为 true
,这是为了方便在React应用运行过程当中区分 Component 和 PureComponent
在分析后续的源码以前,建议小伙伴去看下个人文章:React16源码之React Fiber架构,这篇文章分析了React应用总体的执行流程
本文重点分析 reconciliation阶段 beginWork
函数中的 updateClassComponent
函数的调用(这一部分在 React16源码之React Fiber架构 中重点分析了)
beginWork
函数主要有两部分工做:
一、对Context进行处理
二、根据Fiber节点的tag类型,调用对应的update方法
而tag类型为ClassComponent
的Fiber节点会调用updateClassComponent
函数,咱们来看看updateClassComponent
函数的核心源码
function updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps, renderExpirationTime: ExpirationTime, ) {
...
let shouldUpdate;
if (current === null) {
if (workInProgress.stateNode === null) {
// In the initial pass we might need to construct the instance.
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
复制代码
执行流程以下:
current为null,表示当前组件第一次渲染
判断当前组件是否须要初始化
workInProgress.stateNode === null
表示须要初始化,调用constructClassInstance
、mountClassInstance
两个函数resumeMountClassInstance
函数复用初始化过的实例(React源码也在不断更新,因此这块逻辑比React16源码之React Fiber架构讲的逻辑多了一个复用逻辑)
current不为null,调用updateClassInstance
constructClassInstance
、mountClassInstance
作的工做:
constructClassInstance
主要是初始化组件实例,即调用constructor
构造函数,并注入classComponentUpdater
mountClassInstance
则是调用getDerivedStateFromProps
生命周期函数(v16)及UNSAFE_componentWillMount
生命周期函数从上面的源码能够看到,resumeMountClassInstance
函数和updateClassInstance
函数都会将返回值赋值给shouldUpdate
变量,而shouldUpdate
变量是布尔类型,在后面的流程中,决定是否执行render
函数
这里以updateClassInstance
函数为例来看看源码
function updateClassInstance( current: Fiber, workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): boolean {
// 若是新老props不一致,则会调用 UNSAFE_componentWillReceiveProps 生命周期函数
...
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
// 执行 getDerivedStateFromProps 生命周期函数
...
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextLegacyContext,
);
if (shouldUpdate) {
...
} else {
...
}
...
return shouldUpdate;
}
复制代码
重点关注checkShouldComponentUpdate
函数
function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextLegacyContext, ) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextLegacyContext,
);
stopPhaseTimer();
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
复制代码
执行流程以下:
一、是否有shouldComponentUpdate
生命周期函数,有则调用今生命周期函数并返回结果(shouldUpdate)
二、判断此组件是否为PureComponent
,是则执行shallowEqual
对新老props、新老state进行浅比较,并返回比较结果
三、默认返回true
shallowEqual
函数:
const hasOwnProperty = Object.prototype.hasOwnProperty;
function is(x, y) {
// SameValue algorithm
if (x === y) {
// Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
// Added the nonzero y check to make Flow happy, but it is redundant
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
}
/** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */
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++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
export default shallowEqual;
复制代码
能够看到,shallowEqual
真的就是浅比较,因此对于props、state是复杂数据结构若是使用 PureComponent 每每会致使更新问题
当props、state是简单数据结构的组件适合使用 PureComponent,或者使用 forceUpdate() 来更新复杂数据结构,或者考虑结合 immutable objects 使用,或者直接使用 Component,自定义shouldComponentUpdate
生命周期函数
说到 forceUpdate()
能够顺便看下源码,首先看看 forceUpdate
函数定义,在前面也说过在给组件初始化时,会给组件实例注入classComponentUpdater
,而调用forceUpdate
其实就是调用classComponentUpdater.enqueueForceUpdate
,来看看定义
const classComponentUpdater = {
...
enqueueForceUpdate(inst, callback) {
...
const update = createUpdate(expirationTime);
// !!!
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
复制代码
能够看到,在将update放入队列以前,执行了update.tag = ForceUpdate;
,这个标记将在后面用于标识更新是否为ForceUpdate
,后面的流程与正常更新流程一直,能够参考React16源码之React Fiber架构
咱们再回到updateClassInstance
函数,在执行checkShouldComponentUpdate
函数以前,执行了processUpdateQueue
函数及进行了checkHasForceUpdateAfterProcessing
函数判断
processUpdateQueue
函数主要是遍历updateQueue
,调用getStateFromUpdate
函数
getStateFromUpdate
函数源码以下:
function getStateFromUpdate<State>( workInProgress: Fiber, queue: UpdateQueue<State>, update: Update<State>, prevState: State, nextProps: any, instance: any, ): any {
switch (update.tag) {
case ReplaceState: {
...
}
case CaptureUpdate: {
...
}
// Intentional fallthrough
case UpdateState: {
...
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
复制代码
咱们能够看到,此函数是判断update的tag类型,对于ForceUpdate
类型会将hasForceUpdate
变量设置为true
checkHasForceUpdateAfterProcessing
函数则是返回hasForceUpdate
变量,代码以下:
export function checkHasForceUpdateAfterProcessing(): boolean {
return hasForceUpdate;
}
复制代码
当调用了forceUpdate
函数,不管是否存在shouldComponentUpdate
生命周期函数,不管此组件是否为 PureComponent,都会强制更新,因此应该谨慎使用
以上就是我对React PureComponent的源码的分享,但愿能对有须要的小伙伴有帮助~~~
喜欢个人文章小伙伴能够去 个人我的博客 点star ⭐️