react的渲染更新机制
react源码分为以下几个模块:
-
schedule(调度器)根据得到的优先级(priority)进行调度,决定哪个任务先进行调和(reconciler),
-
reconciler (协调器),发生在render阶段,它的主要任务是找出哪些节点发生了改变,并打上标记(tag)
-
renderer(渲染器),发生在commit阶段将reconciler打好标记的节点渲染到视图上
react的总流程分为几个阶段:
1.入口
2.render阶段,分别为节点执行beginwork和compoleteWork,为节点赋值对应的effecttag对应dom节点的增删改
3.commit阶段,遍历effectList执行对应的dom操作、
渲染
入口
ReactDom.render
Reactdom的render方法,如果当前是根节点会初始化fiberRoot,作为整个应用的虚拟dom节点。
render阶段
FiberNode 内存的dom定义
fiber 节点的属性,这个FiberNode我看它像个双链表,通过child和return链接起来。后面的工作会跟着这个双链表进行工作。
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
//保存节点的信息
this.tag = tag;//对应组件的类型
this.key = key;//key属性
this.elementType = null;//元素类型
this.type = null;//func或者class
this.stateNode = null;//真实dom节点
//连接成fiber树
this.return = null;//指向父节点
this.child = null;//指向child
this.sibling = null;//指向兄弟节点
this.index = 0;
this.ref = null;
//用来计算state
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
//effect相关
this.flags = NoFlags;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
//优先级相关的属性
this.lanes = NoLanes;
this.childLanes = NoLanes;
//current和workInProgress的指针
this.alternate = null;
}
render流程
beginwork
我们的beginwork是随着FiberNode的child一层一层往下遍历,这个过程暂时叫做捕获。主要功能是创建fiber子节点或者复用。
switch (workInProgress.tag) {
case IndeterminateComponent:
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case LazyComponent:
{
var elementType = workInProgress.elementType;
return mountLazyComponent(current, workInProgress, elementType, updateLanes, renderLanes);
}
case FunctionComponent:
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
}
case ClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
}
/// 省略
}
beginWork 会根据workInProgress.tag去处理不同的组件,比如class组件和component,或者来源于this.setState和ForceUpdate。
mount
根据workInProgress.tag去创建不同的子fiber。
以class组件的渲染为例,会执行mountClassInstance,执行finishClassComponent,而finishClassComponent得到我们的class组件的实例(instance),调用render方法,得到当前节点的jsx然后等待reconcile调和得到下一个FiberNode节点,赋值给当前节点的child。
update
更新的话会执行updateClassInstance。根据current fiber进行优化。
completework
而completeWork,是从最底层的child沿着每个节点的return一层一层往上遍历,暂时叫做冒泡。主要功能是处理props和创建dom。
mount
调用createInstance创建真实dom,放到该节点的stateNode上,根据前面beginwork生成得FiberNode树。判断flags,放到RootFiber的finishWork。finishWork就是之后commit阶段遍历的数据。
update
判断stateNode有没有东西,current fiber有没有数据,将处理好的props赋值给updatePayload,保存到workInprogre.updateQuene中
reconciler 调和器(diff)
找出哪些节点发生了改变,并打上标记(flags)
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
function ChildReconciler(shouldTrackSideEffects){
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
var current = newFiber.alternate;
if (current !== null) {
var oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.flags = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
return oldIndex;
}
} else {
// This is an insertion.
newFiber.flags = Placement;
return lastPlacedIndex;
}
}
}
mount和update之间的区别是传参的不同。shouldTrackSideEffects为true时,不会标记flags,
diff
我不深入diff,简单分析,diff涉及的树,有两个,一个时我们更改得到的新的fiber,一个是之前的树。diff的就是这两个树。
- 单节点diff
- key是否一样,key一样根据tag进入不同的,判断type是否一样。key一样type一样返回原节点。
- key一样type不一样,删除该节点,新建节点
- key不一样。删除节点
- 多节点diff
commit阶段
这个阶段主要处理生命周期相关,挂载dom、react的Hooks相关。
commitBeforeMutationEffects
-
执行class组件的 getSnapshotBeforeUpdate
-
调度useEffect
nextEffect = firstEffect; do { { invokeGuardedCallback(null, commitBeforeMutationEffects, null); if (hasCaughtError()) { if (!(nextEffect !== null)) { { throw Error( "Should be working on an effect." ); } } var error = clearCaughtError(); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } } while (nextEffect !== null); / /// commitBeforeMutationEffects if ((flags & Snapshot) !== NoFlags) { setCurrentFiber(nextEffect); // 生命周期相关 commitBeforeMutationLifeCycles(current, nextEffect); resetCurrentFiber(); } if ((flags & Passive) !== NoFlags) { // If there are passive effects, schedule a callback to flush at // the earliest opportunity. if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalPriority$1, function () { // hooks相关 flushPassiveEffects(); return null; }); } }
commitMutationEffects
-
解绑Ref
-
根据effectTag执行相应的dom操作
-
执行componentDIdMount
nextEffect = firstEffect; do { { invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel); if (hasCaughtError()) { if (!(nextEffect !== null)) { { throw Error( "Should be working on an effect." ); } } var _error = clearCaughtError(); captureCommitPhaseError(nextEffect, _error); nextEffect = nextEffect.nextEffect; } } } while (nextEffect !== null); // commitMutationEffects if (flags & Ref) { var current = nextEffect.alternate; if (current !== null) { // 解绑ref commitDetachRef(current); } } var primaryFlags = flags & (Placement | Update | Deletion | Hydrating); // 根据不同类型做dom操作 switch (primaryFlags) { case Placement: { commitPlacement(nextEffect); // Clear the "placement" from effect tag so nextEffect.flags &= ~Placement; break; } case PlacementAndUpdate: { commitPlacement(nextEffect); // Clear the "placement" from effect tag so nextEffect.flags &= ~Placement; // Update var _current = nextEffect.alternate; commitWork(_current, nextEffect); break; } 省略。。。 }
解绑ref为什么是在这里?我们每次进入到这里要么是初始化,我们没有dom,那我们ref绑定什么,所以在这里解绑,然后在我们的dom挂载了。绑定对应的dom
commitLayoutEffects
- 处理生命周期和hooks相关
- 绑定Ref
- 处理setState的回调 commitUpdateQueue
do {
{
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
if (hasCaughtError()) {
if (!(nextEffect !== null)) {
{
throw Error( "Should be working on an effect." );
}
}
var _error2 = clearCaughtError();
captureCommitPhaseError(nextEffect, _error2);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
// commitLayoutEffects
if (flags & (Update | Callback)) {
var current = nextEffect.alternate;
// 处理生命周期相关 和setState的回调,class组件会进入到commitUpdateQueue
commitLifeCycles(root, current, nextEffect);
}
{
if (flags & Ref) {
// 绑定ref
commitAttachRef(nextEffect);
}
}
setState的callback的调度时机,commitUpdateQueue
function commitUpdateQueue(finishedWork, finishedQueue, instance) {
// Commit the effects
var effects = finishedQueue.effects;
finishedQueue.effects = null;
// 遍历
if (effects !== null) {
for (var i = 0; i < effects.length; i++) {
var effect = effects[i];
var callback = effect.callback;
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
}
}
更新
案例
export default class demo1 extends React.Component {
state={
a:1
}
render(){
const{a}=this.state;
return <div>
{a}
<p onClick={()=>{
for(let i=0;i<10;i++){
this.setState({
a:i
})
}
}}>luoqian</p>
</div>
}
}
流程图
入口
setState&forceUpdate
先不管这个updater这个对象是什么时候挂上去的。我们运用class组件的setState,会调用this.updater.enqueueSetState(this,partialState,callback);this为当前类实例,partialState是当前state参数,callback是我们调用之后使用的回调。
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);
var update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback(callback, 'setState');
}
// setState的callback
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
enqueueForceUpdate: function (inst, callback) {
var fiber = get(inst);
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);
/* createUpdate创建的数据结构
var update = {
// 触发时间
eventTime: eventTime,
// 优先级
lane: lane,
// 什么类型
tag: UpdateState,
// children。
payload: null,
//回调
callback: null,
//下一个
next: null
};
*/
var update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
能看到setState是,没对tag做处理。为0.而forceUpdate的话,tag是设置为2.
之后就将这个update推进fiber栈里去。也就是enqueueUpdate()
function enqueueUpdate(fiber, update) {
var updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
var sharedQueue = updateQueue.shared;
var pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
ensureRootIsScheduled
作用是调度,标记root.callbackNode,root上有这个节点就会导致不会进入render阶段。
function ensureRootIsScheduled(root, currentTime) {
var existingCallbackNode = root.callbackNode;
markStarvedLanesAsExpired(root, currentTime);
var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
var newCallbackPriority = returnNextLanesPriority();
if (nextLanes === NoLanes) {
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
root.callbackNode = null;
root.callbackPriority = NoLanePriority;
}
return;
}
// 判断当前进入的优先级和之前的是不是一样的。是就确认阻断渲染。
if (existingCallbackNode !== null) {
var existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
return;
}
cancelCallback(existingCallbackNode);
}
var newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
这里的判断可以解释setState是同步还是异步。
scheduleSyncCallback
function scheduleSyncCallback(callback) {
// 将获取到的render入口放入syncQuene队列内部,用于在刷新的时候执行。进入render阶段
if (syncQueue === null) {
syncQueue = [callback];
// 把flushSyncCallbackQueueImpl交给调度器决定什么时候进行调度。调度时就会执行。
immediateQueueCallbackNode = Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueueImpl);
} else {
syncQueue.push(callback);
}
return fakeCallbackNode;
}
flushSyncCallbackQueueImpl
function flushSyncCallbackQueueImpl() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
var i = 0;
{
try {
var _isSync2 = true;
// 读取之前设置的syncQuene,循环执行,进入render阶段、
var _queue = syncQueue;
runWithPriority$1(ImmediatePriority$1, function () {
for (; i < _queue.length; i++) {
var callback = _queue[i];
do {
callback = callback(_isSync2);
} while (callback !== null);
}
});
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tick
Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue);
throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}
}
流程
用语言来描述setState的流程:
-
调用setState方法,执行,初始化updateQunene,有callback就赋值给当前update的callback。
-
从当前节点跟着return找。找到根节点。
-
将根节点绑定给render入口函数,将render入口函数插入syncQuene队列。(第一个setState)
-
将flushSyncCallbackQueueImpl传递给调度器,建立调度任务。
hrows, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tickScheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue); throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}
}
### 流程
用语言来描述setState的流程:
1. 调用setState方法,执行,初始化updateQunene,有callback就赋值给当前update的callback。
2. 从当前节点跟着return找。找到根节点。
3. 将根节点绑定给render入口函数,将render入口函数插入syncQuene队列。(第一个setState)
4. 将flushSyncCallbackQueueImpl传递给调度器,建立调度任务。
5. 第二个setState在ensureRootIsScheduled 调度时,判定前面的任务和当前任务一样。直接返回。调度器调度执行、flushSyncCallbackQueueImpl进入render阶段。