源码解读
从判断类组件更新的源码开始。
updateClassComponent()
:
触发时机:整个项目的任何位置的state, props, context更改,都会导致该函数被触发,且一个类组件就会触发一次,所以其触发的次数就是类组件的数量。
精简后的源码:
/**
* current: 已经用于渲染的fiber
* workInProgress: 正处于更新阶段的fiber
* Component: 当前的组件,组件的代码
* nextProps: 新的props
* renderExpirationTime: 更新的过期时间
*/
function updateClassComponent(current, workInProgress, Component, nextProps, renderExpirationTime) {
/**
* stateNode: {
* context: {},
* refs: {},
* props: {},
* state: {},
* updater: {
* enqueueForceUpdate: fn,
* enqueueReplaceState: fn,
* enqueueSetState: fn,
* isMounted: fn
* }
* }
*/
var instance = workInProgress.stateNode; //是当前组件的一些信息。
var shouldUpdate;
if (instance === null) {
// Logic for other exceptional cases
} else {
// 关注这里,是否要更新由updateClassInstance方法处理
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderExpirationTime);
}
// 轮转下一个要工作的单元
var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
return nextUnitOfWork;
}
updateClassInstance()
方法:
function updateClassInstance(current, workInProgress, ctor, newProps, renderExpirationTime) {
var instance = workInProgress.stateNode;
cloneUpdateQueue(current, workInProgress); // 拷贝一份更新队列
var oldProps = workInProgress.memoizedProps; // 旧的props
instance.props = workInProgress.type === workInProgress.elementType ? oldProps : resolveDefaultProps(workInProgress.type, oldProps);
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) {
// 触发生命周期ComponentWillReceiveProps
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext);
}
}
resetHasForceUpdateBeforeProcessing();
var oldState = workInProgress.memoizedState; // 旧的state
var newState = instance.state = oldState; // 即将存放新的state,这里还是旧值
// 操作更新队列,同时将新的state更新到workInProgress的memoizedState节点上。
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
newState = workInProgress.memoizedState; // 现在是最新的state了
// 后面解释这里为什么没有返回
// checkHasForceUpdateAfterProcessing()就是返回代码中是不是有调用强制刷新
if (oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) {
// other
return false;
}
if (typeof getDerivedStateFromProps === 'function') { //触发getDerivedStateFromProps()
applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
newState = workInProgress.memoizedState;
}
// 如果代码中有强制更新操作,则不用任何判断都会导致重新render
// 关注这里
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);
if (shouldUpdate) {
// 需要更新
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) {
// 触发声明周期componentWillUpdate
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
}
} else {
// 无需更新的操作
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
} // Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
// instance就是当前的组件,将新的状态更新到组件的对应节点上
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
这段代码有个地方要单独说明一下
if (oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) {
// other
return false;
}
如果满足上面的条件,那么组件也是不会更新的,现在,假设我有2个组件,父组件有两个state
,子组件接收其中的一个,当点击父组件按钮时,更新未被子组件接收的那一个state
,发现子组件的render
也会触发。
class Parent extends React.Component {
state = {
count: 0,
name: 'home'
}
onClick = () => {
this.setState({
count: this.state.count+1
})
}
render() {
return (
<div>
<button onClick={this.onClick} >add count</button>
<Child name={this.state.name}/>
</div>
)
}
}
class Child extends React.Component {
render() {
console.log('我被触发了')
return (
<div>
<p>{this.props.name}</p>
</div>
)
}
}
按照源码的逻辑,<Child />
组件的state
和props
没有更新,也没有context
变化,更没有设置强制刷新,那么应该满足条件直接返回false了呀。断点调试发现oldProps === newProps
是false
。
继续读源码,发现oldProps
和newProps
比较就是workInProgress.memoizedProps
和workInProgress.pendingProps
比较,这两个对象的引用地址是不同的,所以这个if条件一般情况下不成立。(都等于null则成立)
接着往下看checkShouldComponentUpdate()
方法:
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
var instance = workInProgress.stateNode;
// 如果组件显示使用了shouldComponentUpdate,则组件是否需要更新由组件自身决定
if (typeof instance.shouldComponentUpdate === 'function') {
var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
return shouldUpdate;
}
// 重点来了,PureComponent和Component的区别就在这里了
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
}
// 不是PureComponent,始终返回true
return true;
}
如果是React.PureComponent
,则会对该组件的新旧state
和新旧props
做一个浅比较,注意,只是该组件的props
。而如果是React.Component
,则只要是父组件的重新render
,一定会引起所有子组件的重新render
(没有手动控制shouldComponentUpdate
),这就是React.PureComponent
和React.Component
唯一的区别了。
不规范的写法可能导致React.PureComponent 无法正常更新
React.PureComponent
什么时候会更新,则完全取决于shallowEqual()
会怎么处理了,我遇到过一些场景,本来是希望React.PureComponent
可以更新,但是却没有更新,为了保险起见,直接替换为React.Component
了,这样做虽然没有问题,但如果理解了React.PureComponent
如何更新对于我们理解React行为也是有帮助的,继续看一下shallowEqual()
的源码:
// objectIs就是Object.is, 用法参见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is
function shallowEqual(objA, objB) {
if (objectIs(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
} // Test for A's keys different from B.
for (var i = 0; i < keysA.length; i++) {
if (!hasOwnProperty$2.call(objB, keysA[i]) || !objectIs(objA[keysA[i]], objB[keysA[i]])) {
return false;
}
}
return true;
}
浅比较对象的过程如下:
-
Object.is
比较引用地址是否发生变化。对于props和state,他们前后的引用地址是不相等的,所以这里一定为false. - 如果是非对象或者null,则返回false,一般不会出现这种情况。
- 比较前后两次对象的键的长度,如果不一样,即有新增或者删除属性,则返回false
- 遍历对象,比较前后两次对象的键名,如果发生了变化,则返回false。浅比较比较前后两次键值,如果不相等,则返回false
案例1:未浅拷贝对象,导致视图无法更新,比如:
state = {
person: {name: 'hello'}
}
this.setState({
person: Object.assign(this.state.person, {
name: 'jack'
})
})
按上面的比较过程,1,2,3都不满足,走到第4步,发现前后两次的键名不变,且键值也相等,则判断为相等,最后判断为无需更新。不过对象上的值确实变了,所以如果是继承自React.Component
的组件,仍然可以正常看到组件更新。
判断是否需要“更新”后,接下来的工作
到这里我们已经看到了部分生命周期被执行,不过render()
方法还未看到,可以沿着updateClassComponent
继续往下看,当返回了shouldUpdate
标志位之后,控制权交给了finishClassComponent
:
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
var didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
if (!shouldUpdate && !didCaptureError) {
// 如果无需更新,并且当前未发现错误
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderExpirationTime);
}
var instance = workInProgress.stateNode; // Rerender
ReactCurrentOwner$1.current = workInProgress;
var nextChildren;
if (didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {
//....
} else {
nextChildren = instance.render(); // 调用组件的render()方法
}
workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.
return workInProgress.child;
}
再往后还有一段处理过程,才会到componentDidUpdate()
阶段,有兴趣的同学可以自己去看看。
shouldUpdate
的作用到这里就结束了,也就是说,React.Component
和React.PureComponent
的区别也就探讨结束了,再总结一下它们的区别:
对于可能引起更新的动作:
- state更新
- 自身的props更新
- 非自身的props更新,但引起了父组件更新。
对于React.Component
,在不手动控制shouldComponentUpdate()
的情况下,上述三个条件任意一个发生的情况下,有:
-
componentWillReceiveProps(UNSAFE_componentWillReceiveProps)
或者getDerivedStateFromProps
shouldComponentUpdate
-
componentWillUpdate(UNSAFE_componentWillUpdate)
或者getSnapshotBeforeUpdate()
render()
componentDidUpdate()
对于React.Component
,在不手动控制shouldComponentUpdate()
的情况下,1,2两个条件变化和React.Component
一样,但条件3表现和React.Component
不一样:
-
componentWillReceiveProps(UNSAFE_componentWillReceiveProps)
或者getDerivedStateFromProps
shouldComponentUpdate
差点被漏掉的context
在updateClassInstance
中:
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
....
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);
checkHasForceUpdateAfterProcessing()
:
function checkHasForceUpdateAfterProcessing() {
return hasForceUpdate;
}
hasForceUpdate
是一个全局变量,而hasForceUpdate
的值在调用checkHasForceUpdateAfterProcessing()
之前会修改,也就是在processUpdateQueue()
里面,调用了getStateFromUpdate()
方法:
function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
switch (update.tag) {
case ReplaceState:
{}
case CaptureUpdate:
{ }
case UpdateState: // update.tag: 0
{ }
case ForceUpdate: // update.tag: 2
{
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
当更新来自于context时,会将hasForceUpdate
变量置为true
,最后导致shouldUpdate
为true
。
从源码也可以发现,context的变更对于组件是Component
还是PureComponent
是没有关系的。
最后
意外在getStateFromUpdate()
中发现了setState
的相关机制,下一篇文章就从这里开始吧。
网友评论