ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

react的渲染更新机制

2021-04-30 15:05:35  阅读:175  来源: 互联网

标签:渲染 update 更新 react callback nextEffect var null 节点


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操作、
在这里插入图片描述

渲染

入口

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

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

commit阶段

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

在这里插入图片描述

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>
  
  }
}

流程图

在这里插入图片描述

入口

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传递给调度器,建立调度任务。
    hrows, 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传递给调度器,建立调度任务。
5. 第二个setState在ensureRootIsScheduled 调度时,判定前面的任务和当前任务一样。直接返回。调度器调度执行、flushSyncCallbackQueueImpl进入render阶段。

标签:渲染,update,更新,react,callback,nextEffect,var,null,节点
来源: https://blog.csdn.net/luo_qianyu/article/details/116303508

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有