React源码浏览:假造DOM的初始化

媒介

本文的重要目的是浏览源码的历程当中做下笔记和分享给有须要的小伙伴,能够会有马虎和毛病,请读者自行推断,头一次写浏览代码的文章,能够写得有点乱,有什么题目迎接一同讨论一同提高。

React的版本为16.4,主分支的代码,只贴出部份症结代码,完全代码请到Github检察。

假造DOM的初始化

React.createElement

在浏览源码前,我们先提出一个题目,React是如何将假造DOM转换为实在的DOM呢?有题目今后我们才会更有目的的浏览代码,下面我们就带着这个题目去思索。

在日常平凡工作中我们常经常运用JSX语法来建立React元素实例,但他们末了都邑经由历程打包东西编译成原生的JS代码,经由历程React.createElement来建立。比方:

//  class ReactComponent extends React.Component {
//      render() {
//          return <p className="class">Hello React</p>;
//      }
//  }
//  以上代码会编译为:
class ReactComponent extends React.Component {
    render() {
        React.createElement(
          'p',
          { className: 'class'},
          'Hello React'
        )
    }
}

//  <ReactComponent someProp="prop" />
React.createElement(ReactComponent, { someProp: 'prop' }, null);

如许我们就能够建立获得React元素实例。
先来看看createElement的重要源码(部份代码省略):

function createElement(type, config, children) {
  let propName;

  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      //  假如有ref,将他取出来
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      //  假如有key,将他取出来
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        //  将除ref,key等这些特别的属性放到新的props对象里
        props[propName] = config[propName];
      }
    }
  }

  //  猎取子元素
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  //  增加默许props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
const ReactElement = function(type, key, ref, self, source, owner, props) {
  //  终究获得的React元素
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  return element;
};

是否是很简单呢,重假如把我们传进去的东西构成一个React元素对象,而type就是我们传进去的组件范例,他可所以一个类、函数或字符串(如’div’)。

ReactDom.render

虽然我们已获得建立好的React元素,但React有是如何把React元素转换为我们终究想要的DOM呢?就是经由历程ReactDom.render函数啦。

ReactDom.render(
  React.createElement(App),  
  document.getElementById('root')
 );

ReactDom.render的定义:

render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    return legacyRenderSubtreeIntoContainer(
      null,  /* 父组件 */
      element,  /* React元素 */
      container,  /* DOM容器 */
      false,
      callback,
    );
  }

legacyRenderSubtreeIntoContainer先猎取到React根容器对象(只贴部份代码):

...
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
  container,
  forceHydrate,
);
...

因代码过多只贴出经由历程legacyCreateRootFromDOMContainer终究获得的React根容器对象:

const NoWork = 0;

{
    _internalRoot: {
      current: uninitializedFiber,  // null
      containerInfo: containerInfo,  //  DOM容器
      pendingChildren: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    },
    render: (children: ReactNodeList, callback: ?() => mixed) => Work,
    legacy_renderSubtreeIntoContainer: (
      parentComponent: ?React$Component<any, any>,
      children: ReactNodeList,
      callback: ?() => mixed
    ) => Work,
    createBatch: () => Batch
}

在初始化React根容器对象root后,挪用root.render最先假造DOM的衬着历程。

DOMRenderer.unbatchedUpdates(() => {
  if (parentComponent != null) {
    root.legacy_renderSubtreeIntoContainer(
      parentComponent,
      children,
      callback,
    );
  } else {
    root.render(children, callback);
  }
});

因代码量过大,就不一一贴出细致代码,只贴出重要的函数的挪用历程。

root.render(children, callback) -> 
DOMRenderer.updateContainer(children, root, null, work._onCommit) -> 
updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
) ->
scheduleRootUpdate(current, element, expirationTime, callback) ->
scheduleWork(current, expirationTime) ->
requestWork(root, rootExpirationTime) ->
performWorkOnRoot(root, Sync, false) ->
renderRoot(root, false) -> 
workLoop(isYieldy) ->
performUnitOfWork(nextUnitOfWork: Fiber) => Fiber | null ->
beginWork(current, workInProgress, nextRenderExpirationTime)

Fiber

Fiber范例:

type Fiber = {|
  tag: TypeOfWork,
  key: null | string,

  // The function/class/module associated with this fiber.
  type: any,
  return: Fiber | null,

  // Singly Linked List Tree Structure.
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  memoizedProps: any, // The props used to create the output.
  updateQueue: UpdateQueue<any> | null,
  memoizedState: any,
  
  mode: TypeOfMode,

  effectTag: TypeOfSideEffect,
  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,
  
  expirationTime: ExpirationTime,

  alternate: Fiber | null,

  actualDuration?: number,
  actualStartTime?: number,
  selfBaseTime?: number,
  treeBaseTime?: number,

  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
|};

beginWork

按以上函数挪用历程,我们来到beginWork函数,它的作用重假如依据Fiber对象的tag来对组件举行mount或update:

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      markActualRenderTimeStarted(workInProgress);
    }
  }

  if (
    workInProgress.expirationTime === NoWork ||
    workInProgress.expirationTime > renderExpirationTime
  ) {
    return bailoutOnLowPriority(current, workInProgress);
  }

  //  依据组件范例来举行差别处置惩罚
  switch (workInProgress.tag) {
    case IndeterminateComponent:
      //  不确定的组件范例
      return mountIndeterminateComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case FunctionalComponent:
      //  函数范例的组件
      return updateFunctionalComponent(current, workInProgress);
    case ClassComponent:
      //  类范例的组件,我们此次重要看这个
      return updateClassComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case TimeoutComponent:
      return updateTimeoutComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef:
      return updateForwardRef(current, workInProgress);
    case Fragment:
      return updateFragment(current, workInProgress);
    case Mode:
      return updateMode(current, workInProgress);
    case Profiler:
      return updateProfiler(current, workInProgress);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    default:
      invariant(
        false,
        'Unknown unit of work tag. This error is likely caused by a bug in ' +
          'React. Please file an issue.',
      );
  }
}

updateClassComponent

updateClassComponent的作用是对未初始化的类组件举行初始化,对已初始化的组件更新重用

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  const hasContext = pushLegacyContextProvider(workInProgress);
  let shouldUpdate;
  if (current === null) {
    if (workInProgress.stateNode === null) {
      //  假如还没建立实例,初始化
      constructClassInstance(
        workInProgress,
        workInProgress.pendingProps,
        renderExpirationTime,
      );
      mountClassInstance(workInProgress, renderExpirationTime);

      shouldUpdate = true;
    } else {
      //  假如已建立实例,则重用实例
      shouldUpdate = resumeMountClassInstance(
        workInProgress,
        renderExpirationTime,
      );
    }
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}

constructClassInstance

实例化类组件:

function constructClassInstance(
  workInProgress: Fiber,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  const ctor = workInProgress.type;  //  我们传进去的谁人类
  const unmaskedContext = getUnmaskedContext(workInProgress);
  const needsContext = isContextConsumer(workInProgress);
  const context = needsContext
    ? getMaskedContext(workInProgress, unmaskedContext)
    : emptyContextObject;

  const instance = new ctor(props, context);  //  建立实例
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
  adoptClassInstance(workInProgress, instance);

  if (needsContext) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return instance;
}

adoptClassInstance

function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;  //  将实例赋值给stateNode属性
}

mountClassInstance

下面的代码就有我们熟习的componentWillMount性命周期涌现啦,不过新版React已不发起运用它。

function mountClassInstance(
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): void {
  const ctor = workInProgress.type;

  const instance = workInProgress.stateNode;
  const props = workInProgress.pendingProps;
  const unmaskedContext = getUnmaskedContext(workInProgress);

  instance.props = props;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;
  instance.context = getMaskedContext(workInProgress, unmaskedContext);

  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      props,
      instance,
      renderExpirationTime,
    );
    instance.state = workInProgress.memoizedState;
  }

  const getDerivedStateFromProps = workInProgress.type.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') {
    //  React新的性命周期函数
    applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props);
    instance.state = workInProgress.memoizedState;
  }

  if (
    typeof ctor.getDerivedStateFromProps !== 'function' &&
    typeof instance.getSnapshotBeforeUpdate !== 'function' &&
    (typeof instance.UNSAFE_componentWillMount === 'function' ||
      typeof instance.componentWillMount === 'function')
  ) {
    //  假如没有运用getDerivedStateFromProps而运用componentWillMount,兼容旧版
    callComponentWillMount(workInProgress, instance);
    updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null) {
      processUpdateQueue(
        workInProgress,
        updateQueue,
        props,
        instance,
        renderExpirationTime,
      );
      instance.state = workInProgress.memoizedState;
    }
  }

  if (typeof instance.componentDidMount === 'function') {
    workInProgress.effectTag |= Update;
  }
}

finishClassComponent

挪用组件实例的render函数猎取需衬着的子元素,并把子元素举行处置惩罚为Fiber范例,处置惩罚state和props:

function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
  markRef(current, workInProgress);

  const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;

  if (!shouldUpdate && !didCaptureError) {
    if (hasContext) {
      invalidateContextProvider(workInProgress, false);
    }

    return bailoutOnAlreadyFinishedWork(current, workInProgress);
  }

  const ctor = workInProgress.type;
  const instance = workInProgress.stateNode;

  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
  if (
    didCaptureError &&
    (!enableGetDerivedStateFromCatch ||
      typeof ctor.getDerivedStateFromCatch !== 'function')
  ) {
    nextChildren = null;

    if (enableProfilerTimer) {
      stopBaseRenderTimerIfRunning();
    }
  } else {
    if (__DEV__) {
      ...
    } else {
    //  挪用render函数猎取子元素
      nextChildren = instance.render();
    }
  }

  workInProgress.effectTag |= PerformedWork;
  if (didCaptureError) {
    reconcileChildrenAtExpirationTime(
      current,
      workInProgress,
      null,
      renderExpirationTime,
    );
    workInProgress.child = null;
  }
  //  把子元素转换为Fiber范例
  //  假如子元素数目大于一(即为数组)的时刻,
  //  返回第一个Fiber范例子元素
  reconcileChildrenAtExpirationTime(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  //  处置惩罚state
  memoizeState(workInProgress, instance.state);
  //  处置惩罚props
  memoizeProps(workInProgress, instance.props);

  if (hasContext) {
    invalidateContextProvider(workInProgress, true);
  }

  //  返回Fiber范例的子元素给beginWork函数,
  //  一向返回到workLoop函数(看上面的挪用历程)
  return workInProgress.child;
}

workLoop

我们再回头看下workLoop和performUnitOfWork函数(看上面的函数挪用历程),当benginWork对差别范例组件完成响应处置惩罚返回子元素后,workLoop继承经由历程performUnitOfWork来挪用benginWork对子元素举行处置惩罚,从而遍历假造DOM树:

function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      //  遍历整棵假造DOM树
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    while (nextUnitOfWork !== null && !shouldYield()) {
      //  遍历整棵假造DOM树
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }

    if (enableProfilerTimer) {
      pauseActualRenderTimerIfRunning();
    }
  }
}

performUnitOfWork

这个函数很靠近把假造DOM转换为实在DOM的代码啦,当遍历完成一颗假造DOM的子树后(beginWork返回null,即已没有子元素),挪用completeUnitOfWork函数最先转换:

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  startWorkTimer(workInProgress);

  let next;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startBaseRenderTimer();
    }

    next = beginWork(current, workInProgress, nextRenderExpirationTime);

    if (workInProgress.mode & ProfileMode) {
      recordElapsedBaseRenderTimeIfRunning(workInProgress);
      stopBaseRenderTimerIfRunning();
    }
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
  }

  if (next === null) {
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}

completeUnitOfWork

此函数作用为先将当前Fiber元素转换为实在DOM节点,然后在看有没有兄弟节点,如有则返回给上层函数处置惩罚完后再挪用此函数举行转换;不然检察有没有父节点,如有则转换父节点。

function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  while (true) {
    const current = workInProgress.alternate;

    const returnFiber = workInProgress.return;
    const siblingFiber = workInProgress.sibling;

    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      //  挪用completeWork转换假造DOM
      let next = completeWork(
        current,
        workInProgress,
        nextRenderExpirationTime,
      );
      stopWorkTimer(workInProgress);
      resetExpirationTime(workInProgress, nextRenderExpirationTime);

      if (next !== null) {
        stopWorkTimer(workInProgress);
        return next;
      }

      //  处置惩罚完当前节点后
      if (siblingFiber !== null) {
        //  假如有兄弟节点,则将其返回
        return siblingFiber;
      } else if (returnFiber !== null) {
        //  没有兄弟节点而有父节点,则处置惩罚父节点
        workInProgress = returnFiber;
        continue;
      } else {
        return null;
      }
    } else {
      ...
  }
  
  return null;
}

completeWork

依据Fiber的范例举行处置惩罚和抛出毛病,我们重要看HostComponent范例。对HostComponent范例的处置惩罚重假如更新属性,然后经由历程createInstance建立DOM节点,并增加进父节点。

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      recordElapsedActualRenderTime(workInProgress);
    }
  }

  switch (workInProgress.tag) {
    ...
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        //  更新属性
        const oldProps = current.memoizedProps;
        const instance: Instance = workInProgress.stateNode;
        const currentHostContext = getHostContext();
        const updatePayload = prepareUpdate(
          instance,
          type,
          oldProps,
          newProps,
          rootContainerInstance,
          currentHostContext,
        );

        updateHostComponent(
          current,
          workInProgress,
          updatePayload,
          type,
          oldProps,
          newProps,
          rootContainerInstance,
          currentHostContext,
        );

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        if (!newProps) {
          ...
          return null;
        }

        const currentHostContext = getHostContext();
        let wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          if (
            prepareToHydrateHostInstance(
              workInProgress,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        } else {
          //  建立并返回DOM元素
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );

          //  将此DOM节点增加进父节点
          appendAllChildren(instance, workInProgress);

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

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

createInstance

function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string;
  if (__DEV__) {
    ...
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  precacheFiberNode(internalInstanceHandle, domElement);
  updateFiberProps(domElement, props);
  return domElement;
}

createElement

function createElement(
  type: string,
  props: Object,
  rootContainerElement: Element | Document,
  parentNamespace: string,
): Element {
  let isCustomComponentTag;

  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  if (namespaceURI === HTML_NAMESPACE) {

    if (type === 'script') {
      const div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
      const firstChild = ((div.firstChild: any): HTMLScriptElement);
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      domElement = ownerDocument.createElement(type, {is: props.is});
    } else {
      domElement = ownerDocument.createElement(type);
    }
  } else {
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }

  return domElement;
}

总结

到此为止,“React是如何将假造DOM转换为实在DOM”的题目就被处理啦。我们来回忆一下。

  1. 起首我们要经由历程React.createElement函数来将我们定义好的组件举行转换为React元素
  2. 将建立好的React元素经由历程挪用ReactDom.render来举行衬着
  3. ReactDom.render挪用后先建立根对象root,然后挪用root.render
  4. 然后经由多少函数挪用,来到workLoop函数,它将遍历假造DOM树,将下一个须要处置惩罚的假造DOM传给performUnitOfWork,performUnitOfWork再将假造DOM传给beginWork后,beginWork依据假造DOM的范例差别举行响应处置惩罚,并对儿子举行处置惩罚为Fiber范例,为Fiber范例假造DOM增加父节点、兄弟节点守候细节,已轻易遍历树。
  5. beginWork处置惩罚完后返回须要处置惩罚的子元素再继承处置惩罚,直到没有子元素(即返回null),此时performUnitOfWork挪用completeUnitOfWork处置惩罚这颗假造DOM子树,将其转换为实在DOM。
  6. 末了一切的假造DOM都将转为实在DOM。

除此之外另有许多细节守候我们去研讨,比如说React是如何更新和删除假造DOM的?setState的异步完成道理是如何的?今后再写文和人人剖析和讨论。

    原文作者:Windlike
    原文地址: https://segmentfault.com/a/1190000015679450
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞