ReactDom render原理剖析

复习一下常常写的JSX

ReactDOM.render(
    <h1>Hello World</h1>, 
    document.getElementById('root')
);

// babel 转义之后,JSX语法其实是React.createElement()的语法糖
ReactDOM.render(React.createElement(
    'h1', // type--节点类型
    null, // props
    'Hello World' // children
), document.getElementById('root'));

// createElement 部分代码详细过程不做分析
export function createElement(type, config, children) {
  ...
  // 返回一个ReactElement
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
// ReactElement 部分代码
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 标记唯一识别的元素类型
    $$typeof: REACT_ELEMENT_TYPE,
    // 元素的内置类型
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录创建此元素的组件
    _owner: owner,
  };
  ...
  return element; // 最后返回一个element
 }
复制代码

createElement对应的源码部分是ReactElement.js,感兴趣可以自己看看

这里不细说JSX,感兴趣可以看看官网介绍JSX


ReactDom.render 本地渲染过程

版本16之前,不做分析,详细过程参考链接 ReactDom 组件渲染过程

版本16之后,渲染过程分析

声明一个简单组件

class Simple extends React.Component {
  render() {
    return (
      <h1>Simple test</h1>
    )
  }
}
console.log(<Simple />); 
复制代码

经过babel转义之后

var Simple = (function(_React$Component2) {
  // 继承 Component 的属性和方法
  _inherits(Simple, _React$Component2);

  function Simple() {
    _classCallCheck(this, Simple);

    return _possibleConstructorReturn(
      this,
      (Simple.__proto__ || Object.getPrototypeOf(Simple)).apply(this, arguments)
    );
  }

  _createClass(Simple, [
    {
      key: "render",
      value: function render() {
        return _react2.default.createElement("h1", null, "Simple text");
      }
    }
  ]);

  return Simple;
})(_react2.default.Component);

// 直接打印的是 createElement 返回的对象: 包括当前节点的所有信息。 即: `render` 方法
console.log(_react2.default.createElement(Simple, null));

// 实际返回 构造函数。
exports.default = Simple;
复制代码

打印结果

{$$typeof: Symbol(react.element), type: ƒ, key: null, ref: null, props: {…}, …}
  $$typeof:Symbol(react.element)
  key:null
  props:{}
  ref:null
  type:ƒ Simple()
  _owner:null
  _store:{validated: false}
  _self:null
  _source:{fileName: "D:\CodeSpace\Scheele2\src\index.js", lineNumber: 33}
  __proto__:Object
复制代码

组件的挂载

先看chrome浏览器render过程,给大家一个印象,然后根据函数,逐一分析涉及DOM的部分

render --ReactDOM.render

源码位置

const ReactDOM: Object = {
    ...
 render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    return legacyRenderSubtreeIntoContainer(
      null, // parent
      element, // children
      container, // Dom容器
      false, // 渲染标记
      callback, // 回调
    );
  },
}
复制代码

legacyRenderSubtreeIntoContainer

// 结合上方的render函数来看
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
 ...
 let root: Root = (container._reactRootContainer: any);
  if (!root) {
    // 初始化挂载,获得React根容器对象
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
}
}
复制代码

ReactDOM.legacyCreateRootFromDOMContainer

function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean, // 渲染标记
): Root {
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // 第一次渲染,删除其余的所有节点
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      ...
      container.removeChild(rootSibling);
    }
  }
  ...
  const isAsync = false; // 是否异步
 // 返回一个根节点对象
  return new ReactRoot(container, isAsync, shouldHydrate);
}
  ...
// 通过DOMRenderer创建一个root
function ReactRoot(container: Container, isAsync: boolean, hydrate: boolean) {
  const root = DOMRenderer.createContainer(container, isAsync, hydrate);
  this._internalRoot = root;
}
复制代码

源码位置

// createContainer涉及到了React新推出的Fiber
export function createContainer(
  containerInfo: Container,
  isAsync: boolean,
  hydrate: boolean,
): OpaqueRoot {
  return createFiberRoot(containerInfo, isAsync, hydrate);
}
复制代码

源码位置

// 最终返回的对象
export function createFiberRoot(
  containerInfo: any,
  isAsync: boolean,
  hydrate: boolean,
): FiberRoot {
 ...
 // 没有改造成Fiber之前,节点类型可能就几种,Fiber之后嘛...
 // 这里是将节点初始化成Fiber的节点,感兴趣可以看看Fiber,这里不细说
 const uninitializedFiber = createHostRootFiber(isAsync);
 let root;
 // 这个是个 export const enableSchedulerTracing = __PROFILE__;
  if (enableSchedulerTracing) {
   ...
  }else {
   // 最后返回的root对象
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo, // DOM容器
      pendingChildren: null,

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

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }
  uninitializedFiber.stateNode = root;
  return ((root: any): FiberRoot);
}
...
复制代码

unbatchedUpdates

初始化root对象完成之后,调用unbatchedUpdates函数

 // 不管是if,还是else,本质上都是初始化work = new ReactWork(),然后执行updateContainer操作
 DOMRenderer.unbatchedUpdates(() => {
      if (parentComponent != null) { // 如果根节点不为空,将节点渲染进去
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else { // 渲染根节点
        root.render(children, callback);
      }
    });
...
复制代码

ReactRoot.render,updateContainer

ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  ...
  if (callback !== null) {
    work.then(callback);
  }
  // 执行updateContainer函数,返回一个work对象(参数就不解释了吧...)
  DOMRenderer.updateContainer(children, root, null, work._onCommit);
  return work;
};
复制代码

剩下的调用过程(这里主要是react-reconciler部分的代码)

代码量过多,先贴出调用过程

源码位置

// 这些函数调用的过程,可以简单理解成是为了配合Fiber的批度更新以及异步更新而进行的
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) // 这个函数开启一个work流程
复制代码

beginWork

主要作用是根据Fiber对象的tag来对组件进行mount或update

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime; // 更新所需时间
  if (
    !hasLegacyContextChanged() &&
    (updateExpirationTime === NoWork ||updateExpirationTime > renderExpirationTime)
  ) {
    ...
  switch (workInProgress.tag) {
    case IndeterminateComponent: { // 不确定的组件类型
     ...
    }
    case FunctionalComponent: { // 函数类型组件
      ...
    }
    ...
    case ClassComponent: { // 对应我们之前创建的组件 Simple
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      // 返回一个classComponent
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        unresolvedProps,
        renderExpirationTime,
      );
    }
    ...
    case HostRoot: // 对应根节点
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent: // 对应 <h1 />
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText: // 对应Simple test
      return updateHostText(current, workInProgress);
    ... // 后面还有就不一一列举了,感兴趣可以自己看
  }
复制代码

updateClassComponent

主要作用是对未初始化的组件进行初始化,对已初始化的组件进行更新和重用

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
    ...
  if (current === null) {
    if (workInProgress.stateNode === null) {
      // 实例化类组件
      constructClassInstance(
        workInProgress, // Fiber更新进度标识
        Component, // 组件
        nextProps, // props
        renderExpirationTime, // 所需时间
      );
      // 初始化mount
      mountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
      shouldUpdate = true;
    } else {
      // 如果已经创建实例,则重用
      shouldUpdate = resumeMountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
    }
 } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
 }
 ...
}
复制代码

mountClassInstance

主要是挂载一些新的生命周期函数,除了比较熟悉的componentWillMount外,还有dev环境下的警告生命周期函数,以及16后才推出的getDerivedStateFromProps

function mountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): void {
  ...
  if (__DEV__) {
    if (workInProgress.mode & StrictMode) {
      ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
        workInProgress,
        instance,
      );
      ReactStrictModeWarnings.recordLegacyContextWarning(
        workInProgress,
        instance,
      );
    }
    if (warnAboutDeprecatedLifecycles) {
      ReactStrictModeWarnings.recordDeprecationWarnings(
        workInProgress,
        instance,
      );
    }
  }
  // 处理状态更新的操作
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    instance.state = workInProgress.memoizedState;
  }
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') { // 挂载新的生命周期函数
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    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,
        newProps,
        instance,
        renderExpirationTime,
      );
      instance.state = workInProgress.memoizedState;
    }
  }
  if (typeof instance.componentDidMount === 'function') {
    workInProgress.effectTag |= Update;
  }
  // 所以说使用getDerivedStateFromProps这个新的状态函数,第一次加载跟更新都会立即执行
}
复制代码

finishClassComponent

调用组件实例的render函数获取需要处理的子元素,并把子元素处理为Fiber类型,处理state和props

function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
 ...
  //  把子元素转换为Fiber类型,如果子元素为数组的時候,返回第一个Fiber类型子元素
   reconcileChildren( // 感兴趣可以去github看看源码的说明
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  // TODO: Restructure so we never read values from the instance.
  // 处理state和props
  memoizeState(workInProgress, instance.state);
  memoizeProps(workInProgress, instance.props);
  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true);
  }
  // 返回Fiber类型的子元素
  return workInProgress.child;
}
复制代码

workLoop

workLoop遍历虚拟DOM树,调用performUnitOfWork对子元素进行处理。

function workLoop(isYieldy) {
  if (!isYieldy) {
    // 循环遍历整棵树
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // 一直遍历,直到这一次的处理时间用完
    while (nextUnitOfWork !== null && !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}
复制代码

performUnitOfWork

调用beginWork对子元素进行处理返回,没有新工作产生,调用completeUnitOfWork开始转换

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;
  // 检查是否有其它工作需要展开
  startWorkTimer(workInProgress);
  ...
  let next;
  // 判断是否在一个更新时间内
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    if (workInProgress.mode & ProfileMode) {
      // 记录渲染时间
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } 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 returnFiber = workInProgress.return; // 父节点
    const siblingFiber = workInProgress.sibling; // 兄弟节点
    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      // 在一次渲染周期内
      if (enableProfilerTimer) {
        if (workInProgress.mode & ProfileMode) {
          startProfilerTimer(workInProgress);
        }
        // 调用completeWork转换虚拟DOM
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
        if (workInProgress.mode & ProfileMode) {
          // 更新渲染时间
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
      } else {
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
      }
      let next = nextUnitOfWork;
      stopWorkTimer(workInProgress); // 结束一个work
      resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
      ...
      if (next !== null) { // 如果还有新的工作,return回去,继续执行
        stopWorkTimer(workInProgress);
        return next;
      }
      // 这个地方就不注释了,源码里面已经解释的足够清楚
      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.
        const 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;
        }
      }
      ...
      if (siblingFiber !== null) {
        // 如果有兄弟节点,则返回一个兄弟节点
        return siblingFiber;
      } else if (returnFiber !== null) {
        // 完成这次工作
        workInProgress = returnFiber;
        continue;
      } else {
        // 已经遍历到了根部
        return null;
      }
    }  
  }else{
    ... // 感兴趣自己看  
  }
  return null;
}
复制代码

completeWork

根据Fiber节点类型进行处理,渲染真实DOM

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  ...
  switch (workInProgress.tag) {
    ...
    // 只看重点代码
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        // 更新
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );
        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        ...
        const currentHostContext = getHostContext();
        let wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          ...
        } else {
          // 创建并返回DOM元素
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          // 添加节点
          appendAllChildren(instance, workInProgress);
          // 判断是否有其它的渲染器,进行标记
          if (
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
          workInProgress.stateNode = instance;
        }
        if (workInProgress.ref !== null) {
          // 如果有ref,则需要调用
          markRef(workInProgress);
        }
      }
      break;
    }
  }
}
复制代码

简单的总结

  1. 首先我们写的JSX语法,都被Babel进行转义,然后使用React.creatElement进行创建,转换为React的元素
  2. 使用ReactDom.render创建root对象,执行root.render。
  3. 一系列函数调用之后,workLoop在一次渲染周期内,遍历虚拟DOM,将这些虚拟DOM传递给performUnitOfWork函数,performUnitOfWork函数开启一次workTime,将虚拟DOM传递给beginWork。
  4. beginWork根据虚拟DOM的类型,进行不同的处理,将子元素处理为Fiber类型,为Fiber类型的虚拟DOM添加父节点、兄弟节点等(就是转换为Fiber树)。
  5. beginWork处理完一次操作之后,返回需要处理的子元素再继续处理,直到沒有子元素(即返回null),
  6. 此时performUnitOfWork调用completeUnitOfWork进行初始化生命周期的挂载,以及调用completeWork进行DOM的渲染。
  7. completeWork对节点类型进行操作,发现是html相关的节点类型,添加渲染为真实的DOM。
  8. 最后将所有的虚拟DOM,渲染为真实的DOM。

组件的卸载 todo.

服务端渲染过程 todo.

未来的瞎扯

在查看源码的过程中,发现涉及异步的操作,都写上了unstable,而这些异步的操作,才是Fiber这个利器展现獠牙的时候,在未来的某个时刻,我会补上这部分的东西

ps: 有不对的地方欢迎斧正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值