react 源码解读3 react-dom

2,110 阅读11分钟

创建更新

创建更新的方式

  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

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

小结

  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.js

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 会创建 一个fiber

  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树串联起来,然后提供一个高效、方便的遍历方式

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

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

点击进入 下图在线demo

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的状态进行更新渲染。