react的渲染更新机制

react源码分为以下几个模块:

  1. schedule(调度器)根据得到的优先级(priority)进行调度,决定哪个任务先进行调和(reconciler),
  2. reconciler (协调器),发生在render阶段,它的主要任务是找出哪些节点发生了改变,并打上标记(tag)
  3. renderer(渲染器),发生在commit阶段将reconciler打好标记的节点渲染到视图上

react的总流程分为几个阶段:

1.入口

2.render阶段,分别为节点执行beginwork和compoleteWork,为节点赋值对应的effecttag对应dom节点的增删改

3.commit阶段,遍历effectList执行对应的dom操作、

react usestate 获取更新后的值_赋值

渲染

入口

ReactDom.render

react usestate 获取更新后的值_赋值_02

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流程

react usestate 获取更新后的值_dom操作_03

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的就是这两个树。

  1. 单节点diff
  • key是否一样,key一样根据tag进入不同的,判断type是否一样。key一样type一样返回原节点。
  • key一样type不一样,删除该节点,新建节点
  • key不一样。删除节点
  1. 多节点diff

commit阶段

这个阶段主要处理生命周期相关,挂载dom、react的Hooks相关。

react usestate 获取更新后的值_赋值_04

commitBeforeMutationEffects
  1. 执行class组件的 getSnapshotBeforeUpdate
  2. 调度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
  1. 解绑Ref
  2. 根据effectTag执行相应的dom操作
  3. 执行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
  1. 处理生命周期和hooks相关
  2. 绑定Ref
  3. 处理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>
  
  }
}

流程图

react usestate 获取更新后的值_dom操作_05

入口

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的流程:

  1. 调用setState方法,执行,初始化updateQunene,有callback就赋值给当前update的callback。
  2. 从当前节点跟着return找。找到根节点。
  3. 将根节点绑定给render入口函数,将render入口函数插入syncQuene队列。(第一个setState)
  4. 将flushSyncCallbackQueueImpl传递给调度器,建立调度任务。
1.  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阶段。