react 源码解读3 react-dom

建立更新

建立更新的方式

  1. reactDom.render || reactDom.hydrate
  2. setState
  3. forceUpdate

源码文件目录

  1. react/react-dom/src/ReactDom.js reactDom源码
  2. react/react-dom/src/client 客户端渲染
  3. react/react-dom/src/server node.js平台用到服务器端渲染
  4. react/react-dom/src/shared server和client 都会用到的公共包

ReactDom

const ReactDom: Object = {
    // 服务端渲染使用hydrate 和render惟一的区别是 第四个参数的区别
    hydrate(
        element: React$Element<any>,
        container: DOMContainer,
        callback: ?Function,
    ){
        return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          true,
          callback,
        );
    },
    render(
        element: React$Element<any>, // reactELement
        container: DOMContainer, // 要挂载到哪一个dom节点
        callback: ?Function, //应用渲染结束后 调用callback
    ){
        return legacyRenderSubtreeIntoContainer(
            null, // parentComponent
            element,
            container,
            false,
            callback,
        )
    }
}
复制代码

legacyRenderSubtreeIntoContainer

// 渲染Dom Tree到挂载的container节点上
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any,any>, children: ReactNodeList, container: DomContainer, forceHydrate: boolean, callback: ?function ){
    // container 是咱们传入的dom节点 判断container 是否有_reactRootContainer属性 
    // 正常状况下刚开始写入组装的dom标签是不会有_reactRootContainer属性的 因此第一次渲染 根节点root是不存在的
    let root: _ReactSyncRoot = (container._reactRootContainer: any);
    let fiberRoot;
    
    if(!root){
    // 建立一个root对象并赋值container._reactRootContainer
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
            container,
            forceHydrate
        )
        // 结合 ReactSyncRoot 函数 this._internalRoot = root; fiberRoot 为 createContainer函数返回结果
        fiberRoot = root._internalRoot;
        
        // Initial mount should not be batched. 不使用批量更新 
        unbatchedUpdates(() => {
          updateContainer(children, fiberRoot, parentComponent, callback);
        });
    }
    return getPublicRootInstance(fiberRoot);
}

function getPublicRootInstance( container: OpaqueRoot, ): React$Component<any, any> | PublicInstance | null {
// containerFiber 这里是一个fiber对象 
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}
复制代码

legacyCreateRootFromDOMContainer

function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot {
    // 是否须要复用老节点并和新渲染的节点合并
    const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    if (!shouldHydrate) {
        let  rootSibling;
        // 删除container 下的全部子节点
        while((rootSibling = container.lastChild)){
            container.removeChild(rootSibling);
        }
    }
    
    return new ReactSyncRoot(
        container,
        LegacyRoot,
        shouldHydrate ? {
          hydrate: true,
        }
      : undefined,
    )
}
复制代码

shouldHydrateDueToLegacyHeuristic

// 若是 forceHydrate为false 则在render的时候调用shouldHydrateDueToLegacyHeuristic方法
// ROOT_ATTRIBUTE_NAME : 'data-reactroot' 为老版本的服务端渲染 的常量
// 会在root节点下的第一个节点加上 data-reactroot 属性标识目前应用是否有服务端渲染
function shouldHydrateDueToLegacyHeuristic (container){
    const rootElment = getReactRootElementInContainer(getReactRootElementInContainer);
    return !!(
        rootElement &&
        rootElement.nodeType === ELEMENT_NODE &&
        rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
  );
}

// 若是container.nodeType是 DOCUMENT_NODE 则返回 document 
// 不然返回第一个子节点 经过判断 是否返回document 来判断是否须要一个 Hydrate
function getReactRootElementInContainer (container:any) {
    if(container.nodeType === DOCUMENT_NODE){
        return container.documentElement;
    }else{
        return container.firstChild;
    }
}
复制代码

ReactSyncRoot

function ReactSyncRoot ( container: DOMContainer, tag: RootTag, options: void | RootOptions, ) {
    // Tag is either LegacyRoot or Concurrent Root
  const hydrate = options != null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;
    // 建立了一个 fiberRoot 进行赋值
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  this._internalRoot = root;
}
复制代码

updateContainer

function updateContainer ( element:ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any,any>, callback: ?Function, ) {
   // container.current 对应一个fiber对象
    const current = container.current;
    const currentTime = requestCurrentTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    // expirationTime 很是重要 优先级任务更新 拥有复杂计算
    const expiriationTime = computeExpiriiationForFiber(currentTime,current,suspenseConfig);
    return updateContainerAtExpirationTime(
        element,
        container,
        parentComponent,
        expirationTime,
        suspenseConfig,
        callback,
  );
}
复制代码

updateContainerAtExpirationTime

// 
function updateContainerAtExpirationTime ( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) {
    // 
    return scheduleRootUpdate(
        current,
        element,
        expirationTime,
        suspenseConfig,
        callback,
    );
}
复制代码

scheduleRootUpdate

function scheduleRootUpdate( current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) {
    // 建立 update对象 用来标记react应用 须要更新的地点
    const update = createUpdate(expiriationTime,suspenseConfig);
    // 设置update属性 初次渲染
    update.payload = {element};
    // enqueueUpdate 把 update对象加入到fiber对象上对应的 update enqueue 里
    // setState forceUpdate的时候都会调用
    enqueueUpdate(current, update);
    // 开始进行任务调度 任务优先级概念 在同一时间有不一样的任务优先级的任务在队列里, 
    // 则须要有一个任务调度器在里面按照优先级先执行优先级高的任务再执行任务优先级低的任务
    scheduleWork(current, expirationTime);
    return expirationTime;
}
复制代码

react-reconciler

执行节点调和、任务调度的操做javascript

小结

  1. 建立react-root,同时建立fiber-root并自动初始化一个fiber对象
  2. 在root上建立expiriation-time
  3. expiriation-time建立完以后,建立update更新的对象,把这个更新的对象放在root节点上面以后就进入了一个更新的过程
  4. 调度整个任务更新

react-fiber-root

fiberRoot 概念

  1. 整个应用的起点
  2. 包含应用挂载的目标节点 (container)
  3. 记录整个应用更新过程中的应用信息。eg:各类不一样类型的expiriationTime,异步调度过程中的callback

目录

react-reconciler/src/ReactFiberRoot.js
react-reconciler/src/ReactFiber.js
react-reconciler/src/ReactUpdateQueue.jsjava

createFiberRoot

function createFiberRoot( containerInfo:any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot {
    const root: FiberRoot = (new FiberRootNode(containerInfo,tag,hydrate) : any);
    if (enableSuspenseCallback) {
        root.hydrationCallbacks = hydrationCallbacks;
      }
  
  // root节点的fiber 
  // current 是 uninitializedFiber
  // 每个react element节点都会对应一个fiber对象 所以每个fiber对象也会有一个树结构
  // root.current 是这个fiber对象树结构的顶点
    const uninitializedFiber = createHostRootFiber(tag);
    root.current = uninitializedFiber;
    uninitializedFiber.stateNode = root;
    
    return root;
}

// FiberRoot 对象上有哪些信息 属性
function FiberRootNode(containerInfo, tag, hydrate) {
  this.tag = tag;
  // 对应root节点 对应的fiber对象
  this.current = null;
  // react dom上的root节点 render里接收的第二个参数
  this.containerInfo = containerInfo;
  // 只有在持久的更新中会用到 react-dom中不会被用到
  this.pendingChildren = null;
  this.pingCache = null;
  this.finishedExpirationTime = NoWork;
  // 用来记录一个更新渲染当中 完成了的渲染更新任务 由于在整个树当中会存在各类不一样的更新任务
  // 每个更新渲染咱们都会先渲染优先级最高的任务 优先级最高的任务渲染完成以后 就会是一个finishedWork
  // 标记在应用的root上 更新完以后咱们要吧应用输出到dom节点上面 输出的过程中就是读取finishedWork属性
  this.finishedWork = null;
  // suspense 在renderFunction里 throw一个 promise 任务会被挂起 以后渲染suspense 组件的callback 
  // 等到promise result以后就会把result以后的数据显示出来 timeoutHandle来帮助记录这个过程中超时的状况
  this.timeoutHandle = noTimeout;
  // 只有 ·renderSubtreeIntoContainer·时候 才会有用 顶层context对象
  this.context = null;
  this.pendingContext = null;
  // 应用是否要和原来的dom节点进行合并的标志
  this.hydrate = hydrate;
  this.firstBatch = null;
  this.callbackNode = null;
  this.callbackExpirationTime = NoWork;
  this.firstPendingTime = NoWork;
  this.lastPendingTime = NoWork;
  this.pingTime = NoWork;

  if (enableSchedulerTracing) {
    this.interactionThreadID = unstable_getThreadID();
    this.memoizedInteractions = new Set();
    this.pendingInteractionMap = new Map();
  }
  if (enableSuspenseCallback) {
    this.hydrationCallbacks = null;
  }
}
复制代码

react-fiber

fiber 是什么

react-fiberRoot 会建立 一个fibernode

  1. 每个ReactElment对应一个fiber对象
  2. 记录节点的各类状态,例如Class Component里的props、state都是记录在fiber对象上的,在fiber更新以后才会更新到Class Component的this.state,this.props里面。实际上更新state和props不是在class Component上更新的。而是在fiber对象这个节点更新以后才把这个属性放到this上。由于这才给react实现hooks提供方便。由于hooks用在 function Component上,function Component是没有this的,是在fiber对象更新后,再让function Component拿到这个更新了的state和props
  3. 串联整个应用造成树结构。在fiber里能够记录整个应用的状态,把每一个节点串联起来

fiber tree 单向链表的串联方式

double buffer 参考连接 react-fiber 经过单向链表的数据结构把整个fiber树串联起来,而后提供一个高效、方便的遍历方式 react

图一中的 uninitializedFiber 是指图二的 RootFiber RootFiber 能够经过child 拿到整个app tree节点属性 在fiber tree的遍历过程中,只会记录第一个子节点A,其余的子节点都会被当作A的兄弟节点sibling存在。当找不到sibling和child的时候将再也不遍历typescript

fiber 的数据结构 含义

// fiber对应一个被处理或者已经处理了的组件 一个组件能够有一个或者多个Fiber
export type Fiber = {|

  // tag用于区分 fiber的不一样类型,标记不一样的组件类型
  tag: WorkTag,

  // ReactElement里面的key
  key: null | string,

  // ReactElement.type,也就是咱们调用`createElement`的第一个参数
  elementType: any,

  // 异步组件resolved以后返回的内容,通常是`function`或者`class`
  type: any,

  // 对应节点的实例 class Component 对应的是class Component实例,Dom节点就对应的Dom节点的实例。function Component没有实例 因此没有stateNode
  stateNode: any,
  return: Fiber | null,

  // 单链表结构树结构.
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,

  // The ref last used to attach this node.
  // I'll avoid adding an owner field for prod and model that as functions.
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

// 在setState以后 pendingProps 里存的新的props memoizedProps存的老的props
  pendingProps: any, 
  memoizedProps: any, 

  // 在setState或者forceUpdate 建立更新以后就会存在此队列里
  updateQueue: UpdateQueue<any> | null,

  memoizedState: any,

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,
 
  mode: TypeOfMode,
  // Effect
  // 标记最终dom节点要进行哪些更新的工具
  // 标记是否执行组件生命周期的内容
  effectTag: SideEffectTag,

  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  // 当前任务调度产生的过时时间
  expirationTime: ExpirationTime,
  childExpirationTime: ExpirationTime,

  // current <=> wokInprogress 在开始更新和 更新到dom节点上时候 进行状态交换 不须要每次更新都建立 新的对象
  // 一个复制的过程来保持这个两个对象都存在 在react当中叫 double buffer
  // fiber与workInProgress互相持有引用,把current指针指向workInProgress tree,丢掉旧的fiber tree。
  // 旧fiber就做为新fiber更新的预留空间,达到复用fiber实例的目的。
  alternate: Fiber | null,
  
  
  actualDuration?: number,
  actualStartTime?: number,
  selfBaseDuration?: number,
  treeBaseDuration?: number,
  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
  _debugNeedsRemount?: boolean,

  _debugHookTypes?: Array<HookType> | null,
|};
复制代码

react-update-and-updateQueue

什么是update

  1. 用于记录组件状态的改变的对象
  2. 存放于 fiber对象 UpdateQueue中 ,UpdateQueue是一个单向链表的结构。一个Queue中可能会存在多个update,在此次更新当中会根据这些update结果算出最终的新的state的结果
  3. 多个Update能够同时存在

Update的建立

function createUpdate ( expirationTime:ExpirationTime, suspenseConfig:null | SuspenseConfig, ): Update<*>{
    let update:Update<*> =  {
   
        expirationTime,
   
        suspenseConfig,
   
        tag:UpdateState,
   
        payload:null,
    
        callback:null,
    
        next:null,
    
        nextEffect:null,
    }
}
复制代码

Update && updateQueue 的数据结构

export type Update<State> = {
  /** 更新的过时时间 */
  expirationTime: ExpirationTime,
  /** Suspense组件参数配置 */
  suspenseConfig: null | SuspenseConfig,
  
  /** * 指定更新类型,值为如下几种 * export const UpdateState = 0; 更新state * export const ReplaceState = 1;替换state * export const ForceUpdate = 2;强制更新state * export const CaptureUpdate =3; * 在渲染的过程中若是出现渲染错误被捕获了, ErrorBoundary * 组件捕获渲染错误后经过 CaptureUpdate 状态 从新渲染ErrorBoundary内节点 */
  tag: 0 | 1 | 2 | 3,
  
  /** * 要更新的内容 * 例如 把整个element Dom Tree 节点 渲染到payload节点上 */
  payload: any,
  /** 对应回调 例如 setState接收的第一个参数 render都有 */
  callback: (() => mixed) | null, /** * 指向下一个更新 * update是总体存放在updateQueue中,updateQueue是一个相似于单项链表的结构 * 每个update都有一个next,这个next指向下一个updateupdateQueue会有一个 * firstUodatelastUpdate,记录这个单项链表的开头和结尾,这中间的开头和结尾都 * 是经过next串联起来一一对应,把整个update 单链表的结构链接起来 * 在updateQueue中 先读取firstUodate对应的update,而后从第一个updatenext查找下一个update,直到读取到lastUpdate为止 这就是updateQueue队列的执行顺序 */ next: Update<State> | null, /** 指向下一个更新 nextEffect: Update */ nextEffect: Update<State> | null, //DEV only priority?: ReactPriorityLevel, }; export type UpdateQueue<State> = { // 每次应用渲染更新完成以后 baseState记录最新state 在下次更新时候直接读取baseState // 下次计算更新直接在此基础上计算 而不是获取最初state baseState: State, // 队列里的第一个update firstUpdate: Update<State> | null, // 队列里的最后一个update lastUpdate: Update<State> | null, // 第一个捕获类型的 update firstCapturedUpdate: Update<State> | null, // 最后一个捕获类型的 update lastCapturedUpdate: Update<State> | null, firstEffect: Update<State> | null, lastEffect: Update<State> | null, firstCapturedEffect: Update<State> | null, lastCapturedEffect: Update<State> | null, }; 复制代码

enqueueUpdate

// 建立或者更新fiber 上的update enqueue 
// 先 判断 fiber 上alternate 是否有缓存的旧fiber 若是有 则建立两个队列 
// 若是两个队列相同 或只有一个队列 则把update对象推入queue1中
// 若是两个队列不一样 则将update更新入两个queue中
function enqueueUpdate<State>( fiber:Fiber, update:Update<State> ){
  // Update queues are created lazily.
    const alternate = fiber.alternate;
    let queue1;
    let queue2;
    
    if(alternate === null){
    // 在这里理解为 alternate至关于 workInProgress ,fiber 至关于 current
    // 若是alternate 不存在 则表明 这是第一次建立 
    // 第一次渲染 reactDom.render的时候执行
        queue1 = fiber.updateQueue;
        queue2 = null;
        // 若是此时在初始化 没有updateQueue 则建立updateQueue
        if(queue1 === null){
            queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState)
        }
    }else{
     // There are two owners.
     // workInProgress 存在记录 则表明建立过了
        queue1 = fiber.updateQueue;
        queue2 = alternate.updateQueue;
        
        if(queue1 === null){
            if(queue2 === null){
                queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
                queue2 = alternate.updateQueue = createUpdateQueue(
                  alternate.memoizedState,
                );
            }else{
                queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
            }
        }else{
            if(queue2 === null){
                queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
            }
        }
    }
    
    if(queue2 === null || queue1 === queue2 ){
        // 初次渲染 只有一个queue
        appendUpdateToQueue(queue1,update)
    }else{
        // 性能优化 避免重复的update
        if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
             appendUpdateToQueue(queue1, update);
             appendUpdateToQueue(queue2, update);
        }else{
            // 两个队列的lastUpdate 都存在时候 他的lastUpdate 应该是相同的对象,因此 只用将update 推入 queue1
            appendUpdateToQueue(queue1, update);
            // 改变queue2 lastUpdate 的指针
            queue2.lastUpdate = update;
        }
    }
    
}
复制代码

createUpdateQueue

export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    baseState,
    firstUpdate: null,
    lastUpdate: null,
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}
复制代码

appendUpdateToQueue

// 将当前update对象放入 UpdaQueue的最后 
function appendUpdateToQueue<State>( queue: UpdateQueue<State>, update: Update<State>, ) {
  // Append the update to the end of the list.
  if (queue.lastUpdate === null) {
    // Queue is empty 队列为空 直接把lastUpdate 赋值为firstUpdate
    queue.firstUpdate = queue.lastUpdate = update;
  } else { 
    // 在queue中添加新的update
    // 因此把当前update的lastUpdate的next 指向 新加的update 
    // 当前队列queue的lastUpdate 为新加的update
    queue.lastUpdate.next = update;
    queue.lastUpdate = update;
  }
}
复制代码

点击进入 下图在线demopromise

react-expiriation-time

expiriation-time是什么

异步执行的任务优先级都是较低的,为防止这个任务一直被打断,一直不能执行,因此react设置了一个expiraton-time。在某一个expiraton-time时间以前该任务能够被打断,当时间过时这个任务就会被强制执行。缓存

expiriationTime的计算方式

diffrent-expiraton-time

expiriation-time种类

  1. Sync模式:优先级最高的。该任务建立更新完成以后就要立马更新到dom节点上。是一个建立即更新的流程
  2. 异步模式:会进行调度,会有一系列复杂的操做在里面,可能会被中断,他会有一个计算的过时时间,根据优先级级别来计算对应的过时时间。
  3. 指定context

常量定义 -额外发现

二进制字面量 + 异或 进行组合判断 二进制字面量

react-setState-forceUpdate

setState和forceUpdate的核心

给节点的fiber 建立更新。reactDom.render是对于root节点的建立更新,setState和forceUpdate针对的是classComponent的状态进行更新渲染。性能优化

相关文章
相关标签/搜索