react源码解析-jsx转换为ast树及渲染

react源码解析-jsx转换为ast树及渲染挂载

我用react有一段时间了,但是却对其实现的原理一知半解,只知道这样用能行,但是其怎么实现的并不知道。知其然才能对一些性能优化,写代码的时候才能考虑得更充分,将react的利用率达到更高,对代码的质量及项目的架构也会有更好的认识。

jsx转换ast树

  • babel识别jsx语法

    babel在这里具体怎么将jsx转换为react的createElement函数调用,暂且不深说,只需要知道babel在这里的功能是将jsx语法的代码转换为React.createElement()的函数调用
    在https://www.babeljs.cn/repl,可以测试:例:

    const a=<h1 style={{"color":"red"}}>123<div>test</div></h1>
    

    经过编译之后

    "use strict";
    
    var a = React.createElement("h1", {
      style: {
        "color": "red"
      }
    }, "123", React.createElement("div", null, "test"));
    

    可以看出babel在我们正常的项目中,遇到jsx语法就会将jsx代码转换为React.createElement()的函数调用

  • React.createElement()是如何转换为ast树的?
    为了精确的调试和环境的纯净,这里我采用cdn引入react和react-dom的方式来进行测试。

    ReactDOM.render(
    React.createElement(
     'div',
     {className:'red'},
     'Click Me'
    ),
    document.getElementById("root")
    )
    

    最后ast树的样子

    {
    $$typeof: Symbol(react.element)
    key: null
    props: {className: "red", children: "Click Me"}
    ref: null
    type: "div"
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    }
    

    createElement()函数接收参数,并将参数读取转换为ast树的一些所需参数字段,目前我们的数据是比较简洁的,关注点在于props和type就好了

    function createElement(type, config, children) {
        //根据上面的示例代码,type=div,config= {className:'red'},children='Click Me'
      var propName; // Reserved names are extracted
    debugger;
      var props = {};// 我们常用的props 目前组件
      var key = null;//该组件的唯一key
      var ref = null;// 我们的ref
      var self = null;
      var source = null;
    
      if (config != null) {
        if (hasValidRef(config)) {
          ref = config.ref;
        }
    
        if (hasValidKey(config)) {
          key = '' + config.key;
        }
    
        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object
    
        for (propName in config) {
          if (hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
            props[propName] = config[propName];
          }
        }
      } // Children can be more than one argument, and those are transferred onto
      // the newly allocated props object.
    
    	// 当发现arguments的参数大于1的时候。说明是有多个兄弟子元素的,如果等于1的话说明只有一个元素
      var childrenLength = arguments.length - 2;
    
      if (childrenLength === 1) {
          // 直接将props的children设为当前children
        props.children = children;
      } else if (childrenLength > 1) {
        var childArray = Array(childrenLength);
    
        for (var i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
    
        {
          if (Object.freeze) {
            Object.freeze(childArray);
          }
        }
    	// 有多个兄弟元素的话,将兄弟节点放置在一个数组里面,赋值给props的children
        props.children = childArray;
      } // Resolve default props
    
    
      if (type && type.defaultProps) {
        var defaultProps = type.defaultProps;
    
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
    
      {
        if (key || ref) {
          var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
    
          if (key) {
            defineKeyPropWarningGetter(props, displayName);
          }
    
          if (ref) {
            defineRefPropWarningGetter(props, displayName);
          }
        }
      }
    	// ReactElement 返回回来的是我们最终的ast树的结构
      return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
    }
    

    有多个兄弟元素的节点情况

    "use strict";
    // babel 编译过后生成的代码
    var a = React.createElement("h1", {
    style: {
     "color": "red"
    }
    }, "123", React.createElement("div", null, "test"));
    
    // 经过React.createElement()函数调用之后得到的结果
    {$$typeof: Symbol(react.element)
    key: null
    props:
    children: Array(2)
    0: "Click Me"
    1:
    $$typeof: Symbol(react.element)
    key: null
    props: {className: "red", children: "text"}
    ref: null
    type: "p"
    _owner: null
    _store: {validated: true}
    _self: null
    _source: null
    __proto__: Object
    length: 2
    __proto__: Array(0)
    className: "red"
    __proto__: Object
    ref: null
    type: "div"
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    }
    
    
    
    

渲染挂载

调用render方法-渲染
  • render方法主要具备两个功能:
    1. 将ast树转换为真实的dom
    2. 挂载到页面上
ReactDOM.render(
   React.createElement(
      'div',
      {className:'red'},
      'Click Me',
      React.createElement(
         'p',
         {className:'red'},
         'text'
      ),
      React.createElement(
         'p',
         {className:'red'},
         'text'
      )),
   document.getElementById("root")
)

render方法中判断是否可用的dom元素及一些错误判断,此处可以不管。render函数最后调用的是legacyRenderSubtreeIntoContainer(),具体函数实现如下:

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  {
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
  } // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;

    if (typeof callback === 'function') {
      var originalCallback = callback;

      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    } // Initial mount should not be batched.


    unbatchedUpdates(function () {
        // 挂载关注这个函数从这个函数去,会有ast转换为dom树的逻辑和如何插入到dom元素的逻辑
        
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;

    if (typeof callback === 'function') {
      var _originalCallback = callback;

      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);

        _originalCallback.call(instance);
      };
    } // Update


    updateContainer(children, fiberRoot, parentComponent, callback);
  }

  return getPublicRootInstance(fiberRoot);
}

updateContainer()接收的参数有四个,children(之前react转换的ast树), fiberRoot(经过转换的根), parentComponent(父组件,当前没有), callback(渲染完成之后的回调),该函数的具体实现:

function updateContainer(element, container, parentComponent, callback) {
  var current$$1 = container.current;
  var currentTime = requestCurrentTime();

  {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfUnmockedScheduler(current$$1);
      warnIfNotScopedWithMatchingAct(current$$1);
    }
  }

  var suspenseConfig = requestCurrentSuspenseConfig();
  var expirationTime = computeExpirationForFiber(currentTime, current$$1, suspenseConfig);
    // 这里和目前所研讨的主题关系不大,
  return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback);
}

继续进入updateContainerAtExpirationTime函数,按照我的理解。这个函数是为创建更新队列函数做准备,函数翻译过来是在时间线上更新容器

function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback) {
  // TODO: If this is a nested container, this won't be the root.
  var current$$1 = container.current;

  {
    if (ReactFiberInstrumentation_1.debugTool) {
      if (current$$1.alternate === null) {
        ReactFiberInstrumentation_1.debugTool.onMountContainer(container);
      } else if (element === null) {
        ReactFiberInstrumentation_1.debugTool.onUnmountContainer(container);
      } else {
        ReactFiberInstrumentation_1.debugTool.onUpdateContainer(container);
      }
    }
  }

  var context = getContextForSubtree(parentComponent);

  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  return scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback);
}

继续进入scheduleRootUpdate,翻译过来是根据时间线更新根root

这个函数创建了一个更新的队列,然后开始时间线的工作

function scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback) {
  {
    if (phase === 'render' && current !== null && !didWarnAboutNestedUpdates) {
      didWarnAboutNestedUpdates = true;
      warningWithoutStack$1(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
    }
  }

  var update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property
  // being called "element".

  update.payload = {
    element: element
  };
  callback = callback === undefined ? null : callback;

  if (callback !== null) {
    !(typeof callback === 'function') ? warningWithoutStack$1(false, 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback) : void 0;
    update.callback = callback;
  }

  enqueueUpdate(current$$1, update);
    	// 工作的开启时这个函数,这个函数
  scheduleWork(current$$1, expirationTime);
  return expirationTime;
}

scheduleWork时等于scheduleUpdateOnFiber的翻译过来是在fiber(根)上的按照时间更新,单步调试执行会到performSyncWorkOnRoot()这个函数。

function scheduleUpdateOnFiber(fiber, expirationTime) {
    // fiber 是contarner.current,
  debugger;
  checkForNestedUpdates();
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
  var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);

  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }

  checkForInterruption(fiber, expirationTime);
  recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the
  // priority as an argument to that function and this one.

  var priorityLevel = getCurrentPriorityLevel();

  if (expirationTime === Sync) {
    if ( // Check if we're inside unbatchedUpdates
    (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
    (executionContext & (RenderContext | CommitContext)) === NoContext) {
      // Register pending interactions on the root to avoid losing traced interaction data.
      schedulePendingInteractions(root, expirationTime); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
      // root inside of batchedUpdates should be synchronous, but layout updates
      // should be deferred until the end of the batch.
		// 单步调试会到这里 
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, expirationTime);

      if (executionContext === NoContext) {
        // Flush the synchronous work now, unless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of sync mode.
        flushSyncCallbackQueue();
      }
    }
  } else {
    ensureRootIsScheduled(root);
    schedulePendingInteractions(root, expirationTime);
  }

  if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
  // discrete, even inside a discrete event.
  priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
    // This is the result of a discrete event. Track the lowest priority
    // discrete update per root so we can flush them early, if needed.
    if (rootsWithPendingDiscreteUpdates === null) {
      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
    } else {
      var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);

      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
        rootsWithPendingDiscreteUpdates.set(root, expirationTime);
      }
    }
  }
}
var scheduleWork = scheduleUpdateOnFiber; // This is split into a separate function so we can mark a fiber with pending

performSyncWorkOnRoot(),在root上执行异步的工作,也就是执行ast树转换为真实dom的逻辑,单步调试,向下执行。会到一个循环workLoopSync()翻译过来是循环异步工作,因为我们的ast是树,然后当前传入的ast是多个react,ast树,所以需要循环。

function performSyncWorkOnRoot(root) {
  // Check if there's expired work on this root. Otherwise, render at Sync.
  var lastExpiredTime = root.lastExpiredTime;
  var expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
  console.log(root)

  if (root.finishedExpirationTime === expirationTime) {
    // There's already a pending commit at this expiration time.
    // TODO: This is poorly factored. This case only exists for the
    // batch.commit() API.
    commitRoot(root);
  } else {
    (function () {
      if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
        {
          throw ReactError(Error("Should not already be working."));
        }
      }
    })();
    flushPassiveEffects(); // If the root or expiration time have changed, throw out the existing stack
    // and prepare a fresh one. Otherwise we'll continue where we left off.
    console.log(root)
    if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
      prepareFreshStack(root, expirationTime);
      startWorkOnPendingInteractions(root, expirationTime);
    } // If we have a work-in-progress fiber, it means there's still work to do
    // in this root.


    if (workInProgress !== null) {
      var prevExecutionContext = executionContext;
      executionContext |= RenderContext;
      var prevDispatcher = pushDispatcher(root);
      var prevInteractions = pushInteractions(root);
      startWorkLoopTimer(workInProgress);
      debugger;
      do {
        try {
          workLoopSync();
          break;
        } catch (thrownValue) {
          handleError(root, thrownValue);
        }
      } while (true);

      resetContextDependencies();
      executionContext = prevExecutionContext;
      popDispatcher(prevDispatcher);

      if (enableSchedulerTracing) {
        popInteractions(prevInteractions);
      }

      if (workInProgressRootExitStatus === RootFatalErrored) {
        var fatalError = workInProgressRootFatalError;
        stopInterruptedWorkLoopTimer();
        prepareFreshStack(root, expirationTime);
        markRootSuspendedAtTime(root, expirationTime);
        ensureRootIsScheduled(root);
        throw fatalError;
      }

      if (workInProgress !== null) {
        // This is a sync render, so we should have finished the whole tree.
        (function () {
          {
            {
              throw ReactError(Error("Cannot commit an incomplete root. This error is likely caused by a bug in React. Please file an issue."));
            }
          }
        })();
      } else {
        // We now have a consistent tree. Because this is a sync render, we
        // will commit it even if something suspended. The only exception is
        // if the root is locked (using the unstable_createBatch API).
        stopFinishedWorkLoopTimer();
        root.finishedWork = root.current.alternate;
        root.finishedExpirationTime = expirationTime;
        resolveLocksOnRoot(root, expirationTime);
        finishSyncRender(root, workInProgressRootExitStatus, expirationTime);
      } // Before exiting, make sure there's a callback scheduled for the next
      // pending level.


      ensureRootIsScheduled(root);
    }
  }

  return null;
}

workLoopSync函数:


function workLoopSync() {
  debugger;
  // Already timed out, so perform work without checking if we need to yield.
    // 判断  workInProgress 为null的时候就执行完毕了。
   // 第一次进来的时候:workInProgress =其实是root.current.alternate
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current$$1 = unitOfWork.alternate;
  startWorkTimer(unitOfWork);
  setCurrentFiber(unitOfWork);
  var next;

  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
      // next是去查找下一个工作单元,
    next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
	// 判断下一个工作单元为空的话。完成当前单元工作
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(unitOfWork);
  }
// 此处返回的是workInProgress的值,会再次执行下来。
  ReactCurrentOwner$2.current = null;
  return next;
}

function completeUnitOfWork(unitOfWork) {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
    // 将工作树赋值为当前单元工作树,
  workInProgress = unitOfWork;

  do {
    // The current, flushed, state of this fiber is the alternate. Ideally
    // nothing should rely on this, but relying on it here means that we don't
    // need an additional field on the work in progress.
    var current$$1 = workInProgress.alternate;
    var returnFiber = workInProgress.return; // Check if the work completed or if something threw.

    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      setCurrentFiber(workInProgress);
      var next = void 0;

      if (!enableProfilerTimer || (workInProgress.mode & ProfileMode) === NoMode) {
        next = completeWork(current$$1, workInProgress, renderExpirationTime);
      } else {
        startProfilerTimer(workInProgress);
          // 主要看这个函数,具体完成元素渲染操作的。
        next = completeWork(current$$1, workInProgress, renderExpirationTime); // Update render duration assuming we didn't error.

        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
      }

      stopWorkTimer(workInProgress);
      resetCurrentFiber();
      resetChildExpirationTime(workInProgress);

      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        return next;
      }
debugger;
      if (returnFiber !== null && // Do not append effects to parents if a sibling failed to complete
      (returnFiber.effectTag & Incomplete) === NoEffect) {
        // Append all the effects of the subtree and this fiber onto the effect
        // list of the parent. The completion order of the children affects the
        // side-effect order.
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = workInProgress.firstEffect;
        }

        if (workInProgress.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }

          returnFiber.lastEffect = workInProgress.lastEffect;
        } // If this fiber had side-effects, we append it AFTER the children's
        // side-effects. We can perform certain side-effects earlier if needed,
        // by doing multiple passes over the effect list. We don't want to
        // schedule our own side-effect on our own list because if end up
        // reusing children we'll schedule this effect onto itself since we're
        // at the end.


        var effectTag = workInProgress.effectTag; // Skip both NoWork and PerformedWork tags when creating the effect
        // list. PerformedWork effect is read by React DevTools but shouldn't be
        // committed.

        if (effectTag > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            returnFiber.firstEffect = workInProgress;
          }

          returnFiber.lastEffect = workInProgress;
        }
      }
    } else {
      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      var _next = unwindWork(workInProgress, renderExpirationTime); // Because this fiber did not complete, don't reset its expiration time.


      if (enableProfilerTimer && (workInProgress.mode & ProfileMode) !== NoMode) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false); // Include the time spent working on failed children before continuing.

        var actualDuration = workInProgress.actualDuration;
        var child = workInProgress.child;

        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }

        workInProgress.actualDuration = actualDuration;
      }

      if (_next !== null) {
        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        // TODO: The name stopFailedWorkTimer is misleading because Suspense
        // also captures and restarts.
        stopFailedWorkTimer(workInProgress);
        _next.effectTag &= HostEffectMask;
        return _next;
      }

      stopWorkTimer(workInProgress);

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.effectTag |= Incomplete;
      }
    }

    var siblingFiber = workInProgress.sibling;

    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      return siblingFiber;
    } // Otherwise, return to the parent


    workInProgress = returnFiber;
  } while (workInProgress !== null); // We've reached the root.


  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
//函数执行到这里的话。就说明已经渲染完了。
  return null;
}

conpleteWork函数,具体的作用就是去根据当前的workINProgress的tag渲染不同的内容,我们的一个节点是一个文本节点,这里是部分代码

function completeWork(current, workInProgress, renderExpirationTime) {
  var newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
         // 渲染文本节点的逻辑,
    case HostText:
      {
        var newText = newProps;

        if (current && workInProgress.stateNode != null) {
          var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
          // to schedule a side-effect to do the updates.

          updateHostText$1(current, workInProgress, oldText, newText);
        } else {
          if (typeof newText !== 'string') {
            (function () {
              if (!(workInProgress.stateNode !== null)) {
                {
                  throw ReactError(Error("We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."));
                }
              }
            })(); // This can happen when we abort work.

          }

          var _rootContainerInstance = getRootHostContainer();

          var _currentHostContext = getHostContext();

          var _wasHydrated2 = popHydrationState(workInProgress);

          if (_wasHydrated2) {
            if (prepareToHydrateHostTextInstance(workInProgress)) {
              markUpdate(workInProgress);
            }
          } else {
              // 这一个函数创建并且将文本dom节点赋值给当前 workInProgress.stateNode
            workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
            console.log("创建了文字节点",workInProgress)
          }
        }

        break;
      }

  

createTextInstance函数

function createTextNode(text, rootContainerElement) {
    // 运用原生document.创建函数。去创建一个文本节点,返回赋值给workInProgress.stateNode
  return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(text);
}

普通元素节点的创建函数是popHostContext

  case HostComponent:
      {
        popHostContext(workInProgress);
        var rootContainerInstance = getRootHostContainer();
        var type = workInProgress.type;

        if (current !== null && workInProgress.stateNode != null) {
          updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);

          if (enableFlareAPI) {
            var prevListeners = current.memoizedProps.listeners;
            var nextListeners = newProps.listeners;

            if (prevListeners !== nextListeners) {
              markUpdate(workInProgress);
            }
          }

          if (current.ref !== workInProgress.ref) {
            markRef$1(workInProgress);
          }
        } else {
          if (!newProps) {
            (function () {
              if (!(workInProgress.stateNode !== null)) {
                {
                  throw ReactError(Error("We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."));
                }
              }
            })(); // This can happen when we abort work.


            break;
          }

          var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
          // "stack" as the parent. Then append children as we go in beginWork
          // or completeWork depending on we want to add then top->down or
          // bottom->up. Top->down is faster in IE11.

          var _wasHydrated = popHydrationState(workInProgress);

          if (_wasHydrated) {
            // TODO: Move this and createInstance step into the beginPhase
            // to consolidate.
            if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
              // If changes to the hydrated node needs to be applied at the
              // commit-phase we mark this as such.
              markUpdate(workInProgress);
            }

            if (enableFlareAPI) {
              var listeners = newProps.listeners;

              if (listeners != null) {
                updateEventListeners(listeners, workInProgress, rootContainerInstance);
              }
            }
          } else {
              // 他是一个元素节点,在这里创建一个元素标签,
            var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
              // 把所有的孩子放到 instance的下面。建立关系,
            appendAllChildren(instance, workInProgress, false, false); // This needs to be set before we mount Flare event listeners

            workInProgress.stateNode = instance;

            if (enableFlareAPI) {
              var _listeners = newProps.listeners;

              if (_listeners != null) {
                updateEventListeners(_listeners, workInProgress, rootContainerInstance);
              }
            } // Certain renderers require commit-time effects for initial mount.
            // (eg DOM renderer supports auto-focus for certain elements).
            // Make sure such renderers get scheduled for later work.


            if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance, currentHostContext)) {
              markUpdate(workInProgress);
            }
          }

          if (workInProgress.ref !== null) {
            // If there is a ref on a host node we need to schedule a callback
            markRef$1(workInProgress);
          }
        }

        break;
      }

这个函数会根据 workInProgress.child里面的fiber结构树进行遍历组装真实的dom树,那么在什么时候为我们准备好的workInProgress.child的树的转换呢?

 appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
    // We only have the top Fiber that was created but we need recurse down its
    // children to find all the terminal nodes.
    
    var node = workInProgress.child;

    while (node !== null) {
      if (node.tag === HostComponent || node.tag === HostText) {
        appendInitialChild(parent, node.stateNode);
      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
        appendInitialChild(parent, node.stateNode.instance);
      } else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse
        // down its children. Instead, we'll get insertions from each child in
        // the portal directly.
      } else if (node.child !== null) {
        node.child.return = node;
        node = node.child;
        continue;
      }

      if (node === workInProgress) {
        return;
      }

      while (node.sibling === null) {
        if (node.return === null || node.return === workInProgress) {
          return;
        }

        node = node.return;
      }

      node.sibling.return = node.return;
      node = node.sibling;
    }
  };
function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current$$1 = unitOfWork.alternate;
  startWorkTimer(unitOfWork);
  setCurrentFiber(unitOfWork);
  var next;

  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
      // next是去查找下一个工作单元,
    next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
	// 判断下一个工作单元为空的话。完成当前单元工作
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(unitOfWork);
  }
// 此处返回的是workInProgress的值,会再次执行下来。
  ReactCurrentOwner$2.current = null;
  return next;
}

在performUnitOfWork这个函数。每一次在准备下一个工作单元的时候,在beginWork$$1这个函数就会组装workInPregress.child,来看函数内部的实现

function beginWork$1(current$$1, workInProgress, renderExpirationTime) {
  switch (workInProgress.tag) {
    case HostRoot:
          // 重点关注这个函数,是由他实现具体的转换操作
      return updateHostRoot(current$$1, workInProgress, renderExpirationTime);

}
function updateHostRoot(current$$1, workInProgress, renderExpirationTime) {

    // Otherwise reset hydration state in case we aborted and resumed another
    // root.
    // 重点关注这个函数。
    reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime);

  return workInProgress.child;
}
    function reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime) {
  if (current$$1 === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.
    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
      // 在这里组装并赋值给当前的workINProgress
    workInProgress.child = reconcileChildFibers(workInProgress, current$$1.child, nextChildren, renderExpirationTime);
  }
}

第一次进来组装得到的child,为最外面的那一个div,第二次组装的是文本节点,click me,而当前的workInProgress.return.child中的结构就是div包住一个文本节点,她的sibing是p,p的sibing是p,整个需要挂载的元素的fiber结构元素就组装完毕了。到这里,

 appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
    // We only have the top Fiber that was created but we need recurse down its
    // children to find all the terminal nodes.
    
    var node = workInProgress.child;

    while (node !== null) {
      if (node.tag === HostComponent || node.tag === HostText) {
        appendInitialChild(parent, node.stateNode);
      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
        appendInitialChild(parent, node.stateNode.instance);
      } else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse
        // down its children. Instead, we'll get insertions from each child in
        // the portal directly.
      } else if (node.child !== null) {
        node.child.return = node;
        node = node.child;
        continue;
      }

      if (node === workInProgress) {
        return;
      }

      while (node.sibling === null) {
        if (node.return === null || node.return === workInProgress) {
          return;
        }

        node = node.return;
      }

      node.sibling.return = node.return;
        // 
      node = node.sibling;
    }
  };

appendAllChildren当遇到节点是最外面的那个div的时候。就可以根据这个child,去循环遍历出最终需要插入到页面的stateNode,

挂载

挂载逻辑在得到渲染好的frber结构树,

在workLoopSync();继续执行下去会调用到这个函数

function finishSyncRender(root, exitStatus, expirationTime) {
  if (exitStatus === RootLocked) {
    // This root has a lock that prevents it from committing. Exit. If we
    // begin work on the root again, without any intervening updates, it
    // will finish without doing additional work.
    markRootSuspendedAtTime(root, expirationTime);
  } else {
    // Set this to null to indicate there's no in-progress render.
    workInProgressRoot = null;

    {
      if (exitStatus === RootSuspended || exitStatus === RootSuspendedWithDelay) {
        flushSuspensePriorityWarningInDEV();
      }
    }
debugger;
      // 这里有一些调度相关的逻辑,我们重点看react是怎么挂载到dom元素上的。
    commitRoot(root);
  }
}
// 省略部分代码
commitPlacement(){
 var node = finishedWork;

  while (true) {
    var isHost = node.tag === HostComponent || node.tag === HostText;

    if (isHost || enableFundamentalAPI && node.tag === FundamentalComponent) {
      var stateNode = isHost ? node.stateNode : node.stateNode.instance;

      if (before) {
        if (isContainer) {
          insertInContainerBefore(parent, stateNode, before);
        } else {
          insertBefore(parent, stateNode, before);
        }
      } else {
        debugger;
        if (isContainer) {
      	// 我们的示例会进入到这里
          appendChildToContainer(parent, stateNode);
        } else {
          appendChild(parent, stateNode);
        }
      }
    } else if (node.tag === HostPortal) {// If the insertion itself is a portal, then we don't want to traverse
      // down its children. Instead, we'll get insertions from each child in
      // the portal directly.
    } else if (node.child !== null) {
      node.child.return = node;
      node = node.child;
      continue;
    }

    if (node === finishedWork) {
      return;
    }

    while (node.sibling === null) {
      if (node.return === null || node.return === finishedWork) {
        return;
      }

      node = node.return;
    }

    node.sibling.return = node.return;
    node = node.sibling;
  }
}
function appendChildToContainer(container, child) {
  var parentNode;
  debugger;
  if (container.nodeType === COMMENT_NODE) {
    parentNode = container.parentNode;
    parentNode.insertBefore(child, container);
  } else {
    parentNode = container;
    parentNode.appendChild(child);
  } // This container might be used for a portal.
  // If something inside a portal is clicked, that click should bubble
  // through the React tree. However, on Mobile Safari the click would
  // never bubble through the *DOM* tree unless an ancestor with onclick
  // event exists. So we wouldn't see it and dispatch it.
  // This is why we ensure that non React root containers have inline onclick
  // defined.
  // https://github.com/facebook/react/issues/11918


  var reactRootContainer = container._reactRootContainer;

  if ((reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null) {
    // TODO: This cast may not be sound for SVG, MathML or custom elements.
    trapClickOnNonInteractiveElement(parentNode);
  }
}

在这里实际调用dom的添加元素节点的函数。实现了真正的挂载

总结

  1. jsx语法糖的html结构是通过babel将结构解析转换为React.createElement函数调用。

  2. React.createElement做的事情就是生成react的ast树

  3. 渲染,render函数主要做了两件事,一件是将ast树转换为fiber 结构。填入许多调度、更新、diff相关数据,并转换ast树为dom树,一件是挂载。

  4. ast树转换为dom树,先是为我们准备好fiber版本的节点关系结构,然后从下往上的渲染一个一个的元素,最后将所有的节点添加到最外面的那一层dom上面,并赋值给最外层的fiber.stateNode,fiber.stateNode即将插入页面的dom元素。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值