React源码拜读

概览

官网 guide

React-core

React 早期口号是 Rethinking Best Practices(重新思考最佳实践)。背靠 Facebook 的 React,从开始起就不缺关注和用户,而且 React 想要做的是用更好的方式去颠覆前端开发方式(事实上跟早期 jquery 称霸前端,的确是颠覆了)。所以 React 推崇函数式编程(纯组件),数据不可变以及单向数据流。函数式编程最大的好处是其稳定性(无副作用)和可测试性(输入相同,输出一定相同),所以通常大家说的 React 适合大型应用,根本原因还是在于其函数式编程。

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  error,
  warn,

  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

export default React;

ReactElement.js

JSX 代码会被 Babel 编译为 React.createElement,不引入 React 的话就不能使用 React.createElement 了。

function createElement(type, config, ...children) {
  const props = {};
  判断 {key, ref, __self, __source} = config,各自赋值给同名变量
  然后 `hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)`剔除出去这四个内建属性,往props塞 ...config
  判断 `childrenLength = arguments.length - 2` 是否1;
  赋值 props.children `= childrenLe===1?children:Array(childrenLength)` 数组的话就for`childArray[i] = arguments[i + 2];`
  判断 `type && type.defaultProps` 有的话 继续往 props塞 ...type.defaultProps
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
const ReactElement = function (type, key, ref, self, source, owner, props) {
  return {
    $$typeof: REACT_ELEMENT_TYPE, // 通过 $$typeof 来帮助我们识别这是一个 ReactElement,作为一个flag: Symbol.for('react.element')
    type: type, // 内建tag元素
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
};

ReactBaseClasses.js

const emptyObject = {};
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject; // refs 3种传入方式:1. string, 2. ref={el=>this.xxel = el}, 3. this.xxel = React.createRef();ref={this.xxel}
  this.updater = updater || ReactNoopUpdateQueue;
  // !Mark 重点: 下面两个重要原型方法都用到 updater 是 react-dom 中的内容
  // ReactNoopUpdateQueue.js 中的内容,是为了保险起见,用来报警告 对应没有updater的情况下的各种方法【如enqueueSetState 会返回函数 warnNoop(publicInstance, 'setState')】
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
  校验一下partialState是 object 或者 function 或者null
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}
// 让 PureComponent 继承自 Component,使用了典型的寄生组合式。
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true; // 相比Component 唯一增加的属性flag

ReactCreateRef.js

https://react.docschina.org/docs/refs-and-the-dom.html

function createRef() {
  return {
    current: null,
  };
}
// 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
// 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
// 你不能在函数组件上使用 ref 属性,因为他们没有实例。

forwardRef.js

function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
return {
    $$typeof: REACT_FORWARD_REF_TYPE, // 又一个symbol flag: Symbol.for('react.forward_ref')
    render,
  };
}

这样使用(可以拿到 ref)

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

ReactChildren.js

export {
  forEachChildren as forEach,
  mapChildren as map,
  countChildren as count,
  onlyChild as only,
  toArray,
};

重点看 mapChildren

// 使用 React.Children.map(this.props.children, c => [[c, c]])
function mapChildren(children, func, context) {
  判断children==null就 直接返回children;
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
  let escapedPrefix = '';
  if (prefix != null) {
    escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  }
  // 引入了对象重用池的概念,维护一个大小固定(这里为10)的对象重用池,每次从这个池子里取一个对象去赋值,用完了就将对象上的属性置空然后丢回池子。维护这个池子的用意就是提高性能,毕竟频繁创建销毁一个有很多属性的对象会消耗性能
  const traverseContext = getPooledTraverseContext(
    array,
    escapedPrefix,
    func,
    context,
  );
  traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
  releaseTraverseContext(traverseContext);
}
function traverseAllChildren(children, callback, traverseContext) {
  return children == null ? 0 :traverseAllChildrenImpl(children, '', callback, traverseContext);
}
// 把传入的 children 数组通过遍历摊平成单个节点,然后去执行 mapSingleChildIntoContext
// nameSoFar:Name of the key path so far.
// traverseContext:Used to pass information throughout the traversal
// callback:即 mapSingleChildIntoContext(bookKeeping, child, childKey) 函数
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
  判断typeof children:
  是undefined || boolean的话,
  ->赋值children成null -> 调用callback [并且return 1;给subtreeCount计数用]
  string || number || object且children.$$typeof是Symbol.for('react.portal')的话,
  ->调用callback [并且return 1;给subtreeCount计数用]
  // 上面两种情况即 传入的 child 是单个节点,callback 即 mapSingleChildIntoContext 执行这样
  // callback(traverseContext, children, nameSoFar === '' ? '.' + getComponentKey(children, 0) : nameSoFar);
  // getComponentKey(component, index)的逻辑是:如果有.key把component.key处理成('$'+(替换[=,:]成[=0,=2]的原key字符));如果没.key 返回index.toString(36);

  剩下 object且children.$$typeof是Symbol.for('react.element') 这种情况继续往下执行
  /** 上半部分只在children是单节点时执行,下半部分判断children是array或iterator*/
  let child;
  let nextName;
  let subtreeCount = 0; // 存放 当前children层有的children数量
  const nextNamePrefix = nameSoFar === '' ? "." : nameSoFar + ":";
  if (Array.isArray(children)) {
    // 节点是数组的话,就开始遍历数组,并且把数组中的每个元素再递归执行 traverseAllChildrenImpl
    // 也用来摊平数组
    child = children[i];
    nextName = nextNamePrefix + getComponentKey(child, i);
    subtreeCount += traverseAllChildrenImpl(
      child,
      nextName,
      callback,
      traverseContext,
    );
  } else { // 不是数组的话,就看看 children 是否可以支持迭代,???通过 obj[Symbol.iterator] 的方式去取,返回值如果是个函数的话就代表支持迭代,然后逻辑就和之前的一样了
    const iteratorFn = getIteratorFn(children); // getIteratorFn内部逻辑:if(typeof (children[typeof Symbol === 'function' && Symbol.iterator] || children['@@iterator']) === 'function') return 他; 否则return null
    if (typeof iteratorFn === 'function') {
      const iterator = iteratorFn.call(children);
      while (!(step = iterator.next()).done) {
        同样的 执行traverseAllChildrenImpl(...)
      }
    }
    else if(type === 'object') {invariant() children-type无效报警}
  }
  return subtreeCount; //给subtreeCount计数用
}
function mapSingleChildIntoContext(bookKeeping, child, childKey) { // bookKeeping就是我们从对象池子里取出来的东西
  const {result, keyPrefix, func, context} = bookKeeping; // func 即Children.map(child,func)的arg[1]
  let mappedChild = func.call(context, child, bookKeeping.count++);
  if (Array.isArray(mappedChild)) {
    // 对于这种情况的处理:React.Children.map(this.props.children, c => [c, c])
    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); // 回到mapChildren(children, func, context)内的流程,往下会在traverseAllChildrenImpl内部 摊平mappedChild as Array
  }
  else if(mappedChild != null) { // 如果不是数组的话,
    if (isValidElement(mappedChild)) { //判断返回值是否是一个有效的 Element
      mappedChild = cloneAndReplaceKey( // 来自ReactElement.js `cloneAndReplaceKey(oldElement, newKey)`, clone 一份并且替换掉 key
          mappedChild,
          keyPrefix +
            (mappedChild.key && (!child || child.key !== mappedChild.key)
              ? escapeUserProvidedKey(mappedChild.key) + '/' // escapeUserProvidedKey是把'/'替换成'$&/'
              : '') +
            childKey,
      );
    }
    result.push(mappedChild);//最后把返回值放入 result 中,result 即 mapChildren 的返回值。
  }
}

对象重用池相关逻辑

const POOL_SIZE = 10;
const traverseContextPool = [];
function getPooledTraverseContext(
  mapResult,
  keyPrefix,
  mapFunction,
  mapContext
) {
  if (traverseContextPool.length) {
    const traverseContext = traverseContextPool.pop();
    traverseContext.result = mapResult;
    traverseContext.keyPrefix = keyPrefix;
    traverseContext.func = mapFunction;
    traverseContext.context = mapContext;
    traverseContext.count = 0;
    return traverseContext;
  } else {
    return {
      result: mapResult,
      keyPrefix: keyPrefix,
      func: mapFunction,
      context: mapContext,
      count: 0,
    };
  }
}

function releaseTraverseContext(traverseContext) {
  traverseContext.result = null;
  traverseContext.keyPrefix = null;
  traverseContext.func = null;
  traverseContext.context = null;
  traverseContext.count = 0;
  if (traverseContextPool.length < POOL_SIZE) {
    traverseContextPool.push(traverseContext);
  }
}

渲染器

react-dom

将 React 组件渲染成 DOM。它实现了全局 ReactDOMAPI

实例

const APP = () => (
  <div>
    <span></span>
    <span></span>
  </div>
);
ReactDom.render(<APP />, document.querySelector("#root"));

在控制台document.querySelector('#root')._reactRootContainer可以查看
image.png

react-dom/src/client/ReactDOM.js

export default const ReactDOM = {
  createPortal,
  finDOMNode(componentOrElement:Element | ?React$Component<any, any>) => null | Element | Text,
  hydrate(element: React$Node, container: DOMContainer, callback?: Function),
  render(element: React$Element<any>, container: DOMContainer, callback?: Function),
  unstable_renderSubtreeIntoContainer(parentComponent: React$Component<any, any>, element: React$Element<any>, containerNode: DOMContainer, callback?: Function),
  unmountComponentAtNode(container: DOMContainer),
  unstable_createPortal(...args),
  unstable_batchedUpdates:batchedUpdates,
  unstable_interactiveUpdates: interactiveUpdates,
  flushSync: flushSync,

  unstable_createRoot: createRoot,
  unstable_flushControlled: flushControlled,
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { Events: [
    getInstanceFromNode,
    getNodeFromInstance,
    getFiberCurrentPropsFromNode,
    EventPluginHubInjection.injectEventPluginsByName,
    eventNameDispatchConfigs,
    accumulateTwoPhaseDispatches,
    accumulateDirectDispatches,
    enqueueStateRestore,
    restoreStateIfNeeded,
    dispatchEvent,
    runEventsInBatch,
    flushPassiveEffects,
  ]}
}
render(element: React$Element<any>, container: DOMContainer, callback?: Function ) {
    invariant(isValidContainer(container), 'Target container is not a DOM element.');
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false, // forceHydrate 参数,为 true 时是服务端渲染(hydrate 函数内会是true)
      callback,
    );
  },
// 第一部分是没有 root 之前我们首先需要创建一个 root,第二部分是有 root 之后的渲染流程
function legacyRenderSubtreeIntoContainer(
  parentComponent?: React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback?: Function,
) {
  let root = container._reactRootContainer;
  if (!root) { // !mark: 上半部分
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    if (typeof callback === 'function') { 处理一下传进来的callback 即 originalCallback: callback = ()=>{xxx; originalCallback.call(getPublicRootInstance(root._internalRoot))}}
    unbatchedUpdates(() => { // 对于 Root 来说没必要批量更新,直接调用回调函数; 而其他 如 setState 会被优化batch成一次更新,减少了渲染次数。假定不会存在 parentComponent,因为很少有人会在 root 外部加上 context 组件。
      如果parentComponent不存在,就
      root.render(children, callback)
    })
  } else { // !mark: 下半部分
    // 同样是 if (typeof callback === 'function') 那块的 callback逻辑 省略
    // Update
    if (parentComponent != null) {
      root.legacy_renderSubtreeIntoContainer(
        parentComponent,
        children,
        callback,
      );
    } else {
      root.render(children, callback); // 直接执行 render
    }
  }
  return getPublicRootInstance(root._internalRoot);
}
function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): Root {
  如果 不是 forceHydrate,就{
    while ((rootSibling = container.lastChild)) {container.removeChild(rootSibling);}
    const isConcurrent = false;
    return new ReactRoot(container, isConcurrent, shouldHydrate);
  }
}
function ReactRoot(
  container: DOMContainer,
  isConcurrent: boolean,
  hydrate: boolean
) {
  const root = createContainer(container, isConcurrent, hydrate); // 创建了一个 FiberRoot 对象,
  this._internalRoot = root; // 并挂载到了 _internalRoot 上
  // 和 DOM 树一样,fiber 也会构建出一个树结构(每个 DOM 节点一定对应着一个 fiber 对象),FiberRoot 就是整个 fiber 树的根节点,
}
ReactRoot.prototype.render = function (
  children: ReactNodeList,
  callback?: () => mixed
): Work {
  const root = this._internalRoot;
  const work = new ReactWork(); // function ReactWork() {this._callbacks = null;this._didCommit = false; this._onCommit = this._onCommit.bind(this);}
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    work.then(callback); // ReactWork 的功能就是为了在组件渲染或更新后把所有传入ReactDom.render 中的回调函数全部执行一遍
  }
  updateContainer(children, root, null, work._onCommit); // work._onCommit 就是用于执行所有回调函数的
  return work;
};
ReactRoot.prototype.unmount = function (callback?: () => mixed): Work {};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function (
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  callback?: () => mixed
): Work {};
ReactRoot.prototype.createBatch = function (): Batch {};

react-reconciler/src/ReactFiberReconciler.js

export function createContainer(
  containerInfo: Container,
  isConcurrent: boolean,
  hydrate: boolean,
): OpaqueRoot {
  return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent?: React$Component<any, any>,
  callback?: Function,
): ExpirationTime {
  const current = container.current; // container即fiberRoot(),fiberRoot的current 是 rootFiber Node
  const currentTime = requestCurrentTime(); // 计算时间
  const expirationTime = computeExpirationForFiber(currentTime, current); // expirationTime 代表优先级,数字越大优先级越高;同步的优先级最高 export const Sync = MAX_SIGNED_31_BIT_INT = 1073741823; 换算成天 约等于12天
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}
export function updateContainerAtExpirationTime(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent?: React$Component<any, any>,
  expirationTime: ExpirationTime,
  callback?: Function,
) {
  const current = container.current;
  const context = getContextForSubtree(parentComponent); // // 获取 context 并赋值,这里肯定取不到值得,因为 ReactRoot.prototype.render里的 updateContainer(_,_,null,_)
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
  return scheduleRootUpdate(current, element, expirationTime, callback);
}
// 最终计算出一个任务的执行截止时间。只要这个截止时间比 当前已经过的时间 大就可以一直让 React 延后这个任务的执行,以便让更高优先级的任务执行,但是一旦过了任务的截止时间,就必须让这个任务马上执行。
function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback?: Function,
) {
  const update = createUpdate(expirationTime); // 一个固定几个keys的对象 工厂函数,创建个obj包括keys:expirationTime, tag, payload(setState 的第一二个参数), callback, next(用于在队列中找到下一个节点), nextEffect
  update.payload = {element}; // render 的过程中其实也是一次更新的操作。对于render函数 是没有setState的所以也不可能有其第二个参数Payload,所以直接赋值 ReactRoot.prototype.render里的第一参数children
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    catch下 callback-invalid的情况;
    update.callback = callback;
  }
  flushPassiveEffects(); // import from ReactFiberScheduler.js, 与 useEffect 有关
  enqueueUpdate(current, update); // import from ReactUpdateQueue.js  把 update 入队,内部就是一些创建或者获取 queue(链表结构),然后给链表添加一个节点的操作
  scheduleWork(current, expirationTime); // import from ReactFiberScheduler.js, 调度相关

  return expirationTime;
}

react-reconciler/src/ReactFiberScheduler.old.js

// react-reconciler/src/ReactFiberScheduler.js
export const requestCurrentTime = enableNewScheduler // import {enableNewScheduler} from 'shared/ReactFeatureFlags'; constant enableNewScheduler = false,所以我们只看 old 的代码
  ? requestCurrentTime_new
  : requestCurrentTime_old; //import {requestCurrentTime as requestCurrentTime_old} from './ReactFiberScheduler.old.js'
function requestCurrentTime() {
  if (isRendering) {
    return currentSchedulerTime;
  }
  findHighestPriorityRoot();
  if (
    nextFlushedExpirationTime === NoWork ||
    nextFlushedExpirationTime === Never
  ) {
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;
    return currentSchedulerTime;
  }
  return currentSchedulerTime;
}
function recomputeCurrentRendererTime() {
  const currentTimeMs = now() - originalStartTimeMs; // 结果就是 现在离 React 应用初始化时经过了多少时间
  // now()用的scheduler库,即[Performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now)
  // originalStartTimeMs 是 React 应用初始化时就会生成的一个变量,值也是 performance.now()
  currentRendererTime = msToExpirationTime(currentTimeMs); // => currentTimeMs / 10 | 0
}
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
  // 各种if else 条件 省.. =>
  if (expirationContext !== NoWork) {}
  else if (isWorking) {}
  else {
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) expirationTime = computeInteractiveExpiration(currentTime) // 交互事件,优先级较高
      else expirationTime = computeAsyncExpiration(currentTime) // 异步,优先级较低
      if (nextRoot !== null && expirationTime === nextRenderExpirationTime) expirationTime -= 1; // do not update at the same expiration time that is already rendering
    } else expirationTime = Sync // sync update 的情况
  }
  ...
  return expirationTime;
}

react-reconciler/src/ReactFiberExpirationTime.js

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION, // const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
    HIGH_PRIORITY_BATCH_SIZE // const HIGH_PRIORITY_BATCH_SIZE = 100;
  );
}
function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET - // const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
    ceiling(
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, // const UNIT_SIZE = 10;
      bucketSizeMs / UNIT_SIZE // 为了抹平一段时间(这里=10)内的时间差,在抹平的时间差内不管有多少个任务需要执行,他们的过期时间都是同一个,这也算是一个性能优化,帮助渲染页面行为节流
    )
  );
}
function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

react-reconciler/src/ReactFiberRoot.js

// !mark: 在 createFiberRoot 函数内部,分别创建了两个 root,一个叫做 FiberRoot(FiberRootNode),另一个叫做 RootFiber(HostRootFiber),并且它们两者还是相互link的。
export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  const root: FiberRoot = new FiberRootNode(containerInfo, hydrate);
  const uninitializedFiber = createHostRootFiber(isConcurrent);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root; // ,并且它们两者还是相互引用的
  return root;
}

function FiberRootNode(containerInfo, hydrate) {// 内部创建了很多属性,需要了解两个属性,分别是 containerInfo 及 current。
  this.current = null; // 指向 RootFiber。
  this.containerInfo = containerInfo; //代表着容器信息,也就是我们的 document.querySelector('#root')
  this.pendingChildren = null;
  this.pingCache = null;
  ...
  this.hydrate = hydrate;
  this.firstBatch = null;
  ...
  条件
  this.memoizedInteractions = new Set();
  this.pendingInteractionMap = new Map();
}

react-reconciler/src/ReactFiber.js

export function createHostRootFiber(isConcurrent: boolean): Fiber {
  let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
  if (enableProfilerTimer && isDevToolsPresent) {
    mode |= ProfileMode;
  }
  return createFiber(HostRoot, null, null, mode);
}
const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null; // stateNode 保存了每个fiber节点的 DOM 信息

  // Fiber
  // return、child、sibling 这三个属性很重要,它们是构成 fiber 树的主体数据结构,return、child、sibling、index 组成了fiber单链表树结构
  this.return = null; // 代表父 fiber
  this.child = null; // child 代表子 fiber
  this.sibling = null; // sibling 代表下一个兄弟节点,和链表中的 next 一个含义
  this.index = 0; // 代表了当前 fiber 的索引

  this.ref = null;
  // 更新相关
  this.pendingProps = pendingProps; // // 新的 props
  this.memoizedProps = null; // // 旧的 props
  this.updateQueue = null; // // 存储 setState 中的第一个参数
  this.memoizedState = null; // // 旧的 state
  this.contextDependencies = null;

  this.mode = mode;

  // Effects, 更新 DOM 相关
  this.effectTag = NoEffect; // 指这个节点需要进行的 DOM 操作. import {NoEffect} from 'shared/ReactSideEffectTags'; 记录所有的 effect 的,记录是通过位运算来实现的:如新增`effectTag |= Update`,删除`effectTag &= ~Update`
  this.nextEffect = null; // 下一个需要进行 DOM 操作的节点
  this.firstEffect = null; //  第一个
  this.lastEffect = null; // 最后一个

  this.expirationTime = NoWork; // 任务过期时间
  this.childExpirationTime = NoWork;

  this.alternate = null; // 还有一个 alternate 属性很重要,这个属性代表了一个更新中的 fiber,大部分情况下每个 fiber 都有一个替身 fiber,在更新过程中,所有的操作都会在替身上完成,当渲染完成后,替身会代替本身
  if (enableProfilerTimer) {...}
}

该文件下还有个export function createWorkInProgress(current: Fiber, pendingProps: any, expirationTime: ExpirationTime): Fiber {}
在一个 React 应用中,通常来说都有两个 fiebr 树,一个叫做 old tree,另一个叫做 workInProgress tree。前者对应着已经渲染好的 DOM 树,后者是正在执行更新中的 fiber tree,还能便于中断后恢复。两棵树的节点互相引用,便于共享一些内部的属性,减少内存的开销。毕竟前文说过每个组件或 DOM 都会对应着一个 fiber 对象,应用很大的话组成的 fiber 树也会很大,如果两棵树都是各自把一些相同的属性创建一遍的话,会损失不少的内存空间及性能。
当更新结束以后,workInProgress tree 会将 old tree 替换掉,这种做法称之为 double buffering,这也是性能优化里的一种做法

react-dom/src/events

react-native-renderer

实现了 React 和 React Native 的连接。
真正渲染 Native 视图的平台特定代码及组件都存储在 React Native 仓库中。

react-test-renderer

将 React 组件渲染为 JSON 树。这用于 Jest 的快照测试特性。

react-reconciler

“fiber” reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber 从 React 16 开始变成了默认的 reconciler。
流程大致如下:

ReactFiberScheduler.old.js

flushRoot(root: FiberRoot, expirationTime: ExpirationTime)
-> performWorkOnRoot(root, expirationTime, false) // root: FiberRoot, expirationTime: ExpirationTime, isYieldy: boolean,
-> renderRoot(root, isYieldy); // (root: FiberRoot, isYieldy: boolean): void // 这里开始渲染整颗树,这个函数在异步模式下可能会被多次执行,因为在异步模式下 // 可以打断任务。打断也就意味着每次都得回到 root 再开始从上往下循环
-> try { workLoop(isYieldy) } catch (thrownValue) {} // workLoop(isYieldy)
-> nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // performUnitOfWork(workInProgress: Fiber): Fiber | null // 开始组件更新
-> next = beginWork(current, workInProgress, nextRenderExpirationTime); //

ReactFiberBeginWork.js

beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime ): Fiber | null {
  switch(workInProgress.tag) {
    // 21 种 case
    // 几乎每种 case【case 对应列在 ~/shared/ReactWorkTags】 return的func 内 都最终执行reconcileChildren()。例:
    case ClassComponent: {
      // 省略几个const
      return updateClassComponent( // updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps, renderExpirationTime: ExpirationTime )
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
  }
}
-> finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime
  )
-> function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
  ...
  const instance = workInProgress.stateNode;
  nextChildren = instance.render();
  ...
  if (current !== null && didCaptureError) {}
  else reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
}
-> reconcileChildren( // 这里开始diff 算法,生成新的children
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime
  ) {
    if (current === null) {if (current === null) {
      workInProgress.child = mountChildFibers( // 添加fiber
        workInProgress,
        null,
        nextChildren,
        renderExpirationTime,
      );
    }
    else workInProgress.child = reconcileChildFibers( // maybe 复用fiber / delete / remove...
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }

ReactChildFiber.js

function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null
-> ...省略string/number/isObject/iterator...
if (isArray(newChild)) return reconcileChildrenArray(
    returnFiber,
    currentFirstChild,
    newChild,
    expirationTime,
)

reconcileChildrenArray 简化流程

diff 相关。
fiberNode中的属性:

this.return = null; // 代表父 fiber
this.child = null; // child 代表子 fiber
this.sibling = null; // sibling 代表下一个兄弟节点,和链表中的 next 一个含义
this.index = 0; // 代表了当前 fiber 的索引

参考

// 处理子节点是数组的情况
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  expirationTime: ExpirationTime
): Fiber | null {
  let resultingFirstChild: Fiber | null = null;
  let previousNewFiber: Fiber | null = null;

  let oldFiber = currentFirstChild; // 原父节点的第一个子节点
  let lastPlacedIndex = 0;
  let newIdx = 0; // 跟随 newChildren
  let nextOldFiber = null;

  // 第一轮 遍历,根据newIdx 同时对比 新旧二叉树。条件:存在原先的子节点 且 未遍历完需要更新的子节点
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else nextOldFiber = oldFiber.sibling; // 正常来说 nextOldFiber 就是下一个节点了
    const newFiber = updateSlot(
      // 该函数目的:如果 key 相同的话就可以复用 oldFiber。(对应上面第一个if: oldFiber 如果为空的话,就会重新创建一个 fiber)
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      expirationTime
    );
  }
}

虽然有三次 for 循环,但是本质上只是遍历了一次整个 newChild Array。

第一轮遍历的核心逻辑是复用和当前节点索引一致的老节点,一旦出现不能复用的情况就跳出 break current 遍历。

  • 新旧节点都为文本节点,可以直接复用,因为文本节点不需要 key
  • 其他类型节点一律通过判断 key 是否相同来复用或创建节点(可能类型不同但 key 相同)

当第一轮遍历结束后,其中存在两种特殊情况:

  • newChild 已经遍历完: 当 newChild 已经遍历完 只需要把所有剩余的老节点都删除即可。删除的逻辑也就是(设置 effectTag 为 Deletion)
  • 老的节点已经遍历完了:当出现老的节点已经遍历完了的情况时,就会开始第二轮遍历。这轮遍历的逻辑很简单,只需要把剩余新的节点全部创建完毕即可。

第三轮遍历的核心逻辑是找出可以复用的老节点并移动位置,不能复用的话就只能创建一个新的了。
首先把所有剩余的老节点都丢到一个 map 中。
在遍历的过程中会寻找新的节点的 key 是否存在于这个 map 中,

  • 存在即可复用,如果复用成功,就应该把复用的 key 从 oldtree-map 中删掉,并且给复用的节点移动位置(给 effectTag 赋值为 Placement)
  • 不存在就只能创建一个新的。
    此轮遍历结束后,把还存在于 map 中的所有老节点删除

reference


   转载规则


《React源码拜读》 Ryan Who 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
React Native React Native
官网 weex、cordova、flutter、react-native、uniapp 对比https://zhuanlan.zhihu.com/p/103409129 Core Components文档 最常用的 native 组件
2021-05-01
下一篇 
react-HOC 浅谈 react-HOC 浅谈
HOC 其实有两种模式, 一种是『属性代理』属性代理大家很熟悉了,也是大家最常用的一种 HOC 形式,主要是用于修改 props,抽象 state, 调取 refs 等等,就不多赘述了,以上这些 React Hooks 都能完成的更出色.
2021-04-26
  目录