关于javascript:新手也可以读懂的-React18-源码分析一

63次阅读

共计 10326 个字符,预计需要花费 26 分钟才能阅读完成。

哈喽大家好,良久没有跟大家以技术文章的模式见面了。最近自己在应用 React 18 做 Web 我的项目,所以抽空钻研了一下 React 18 的源代码。接下来想做一个 React 18 源码剖析的系列,系列文章会以「demo + 源码」的模式由浅入深地跟大家一起探讨新版本的 React 的技术实现,欢送点赞 / 关注 / 拍砖,一起提高

本系列将默认应用 React v18.1.0 版本,默认运行环境为浏览器,读者可自行到 GitHub 下载 React 源码,敬请注意

本章咱们将探讨 React 我的项目 初始化 的时候做了哪些事件:

Demo

咱们应用 create-react-app 这个官网脚手架创立一个 React 我的项目,而后将 index.js 这个文件批改为以下代码

import {createRoot} from 'react-dom/client';

function App() {return <h1>Hello dan!!!</h1>}

const root = createRoot(document.getElementById('root'))
root.render(<App></App>)

执行 npm start 这个脚本,如果你看到这个非常简单(俊俏)的页面显示进去,那证实我的项目曾经能够失常运行起来了

函数剖析

从 Demo 咱们看到,整个我的项目先通过 createRoot 这个函数创立一个 root 对象,再通过 rootrender 办法将 App 这个组件渲染到网页上

createRoot

咱们先看 createRoot 这个办法具体做了什么事件。这个办法来自 react-dom 这个包。咱们能够在源码中 packages / react-dom / src / client / ReactDOMRoot.js 中找到 createRoot 的具体实现(后面在 ReactDOM.js 做了一些对于环境的条件判断,可先疏忽

createRoot 函数有两个参数 containeroptions,其中 options 是可选参数,本章为了简略起见先不探讨;

该函数大略实现的性能就是:

  1. 创立容器对象 FiberRootNode
  2. 事件委托解决
  3. 依据 FiberRootNode 对象返回 ReactDOMRoot 对象
// 删除了一些烦扰逻辑之后,createRoot 函数大抵如下所示

function createRoot(
  container: Element | DocumentFragment,
  options?: CreateRootOptions,
): RootType {

  let isStrictMode = false;
  let concurrentUpdatesByDefaultOverride = false;
  let identifierPrefix = '';
  let onRecoverableError = defaultOnRecoverableError;
  let transitionCallbacks = null;
  
  // 创立容器对象 `FiberRootNode`
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);
  
  // 事件监听解决
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);
  
  // 依据容器对象 `root` 返回 `ReactDOMRoot` 对象
  return new ReactDOMRoot(root);
}

createContainer 函数实现了 创立容器对象 FiberRootNode 的工作。

这个办法来自 react-reconciler 这个包,能够在 packages / react-reconciler / src / ReactFiberReconciler.old.js 中找到,而这个办法内容也很简略,间接调用了同层级 ReactFiberRoot.old.js 文件的 createFiberRoot 办法来创立并返回一个 FiberRootNode 对象, 也称之为 Fiber 根结点

留神: 这里的 Fiber 根结点Fiber 节点 是有区别的,具体可看上面的各自的定义函数

这里引入一个概念叫做 Fiber,目前咱们只须要对他有个初步的印象:Fiber 节点用于存储 React 组件节点信息(包含 DOM 节点,组件的属性 / state / effect 等)。这里能够简略了解为一个存储信息的 JS 对象,后续章节会具体介绍

function createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride,
identifierPrefix, onRecoverableError, transitionCallbacks) {
  // 创立 FiberRootNode 对象
  // tag 值为 ConcurrentRoot,定义在 packages/react-reconciler/src/ReactRootTags.js 文件中;// tag === ConcurrentRoot === 1,示意“根节点”var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);
  // 倡议看到这里先别着急看前面的代码,先看看上面 FiberRootNode 的定义和构造函数
  
  // 分割线 ************** 分割线 ************** 分割线 ************** 分割线 **************
  
  // 看完 FiberRootNode 的定义之后,接下来马上要创立 Fiber 对象
  // createHostRootFiber 会调用 packages/react-reconciler/src/ReactFiber.old.js 文件中的 createFiber 办法创立一个 `Fiber HostRoot 节点 `
  // `Fiber HostRoot 节点 ` 就是一个 Fiber 对象,只是他的 Tag 等于 3,代表 `HostRoot`
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  
  // 把 `Fiber 根结点 ` 的 current 属性指向刚创立的 `Fiber HostRoot 节点 `
  root.current = uninitializedFiber;
  // `Fiber HostRoot 节点 ` 的 stateNode 属性指向 `Fiber 根结点 `
  uninitializedFiber.stateNode = root;

  // cache 相干的可先疏忽
  var initialCache = createCache();
  retainCache(initialCache);

  root.pooledCache = initialCache;
  retainCache(initialCache);
  
  // 初始化一个 state 对象
  var initialState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: initialCache,
      transitions: null
  };
  uninitializedFiber.memoizedState = initialState;
  
  // 初始化 `Fiber HostRoot 节点 ` 的更新队列
  // 给 Fiber 的 updateQueue 属性赋值
  /**
  var queue = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      interleaved: null,
      lanes: NoLanes
    },
    effects: null
  };
  fiber.updateQueue = queue;
  **/
  initializeUpdateQueue(uninitializedFiber);
  
  // 返回 `Fiber 根结点 `
  return root;
}

FiberRootNode 的定义:

一个构造函数,对象内保留 Fiber 根节点 的信息,可先关注以下几个

  • tag:标识节点类型,此处为 ConcurrentRoot
  • containerInfo:Fiber 根节点 的 DOM 信息,示意在这个 DOM 节点外部渲染以后 React 利用
  • current:保留以后 Fiber 树(后续章节会讲到)
  • 其余属性能够先大抵扫一遍,重要是的后续会一一介绍

    
    // 在 `packages/react-reconciler/src/ReactFiberRoot.old.js` 文件中
    // FiberRootNode 构造函数
    function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError) {
    this.tag = tag;
    this.containerInfo = containerInfo;
    this.pendingChildren = null;
    this.current = null;
    ....
    // 省略其余属性初始化
    ....

Fiber 节点的定义

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // tag 示意 Fiber 类型
  // packages/react-reconciler/src/ReactWorkTags.js 中定义
  this.tag = tag;
  // 写在 jsx 组件上的 key 属性
  this.key = key;
  // createElement 的第一个参数,ReactElement 上的 type
  this.elementType = null;
  // 临时可认为与 elementType 基本一致
  this.type = null;
  // fiber 节点对应的 DOM 节点
  this.stateNode = null;

  // Fiber 构造
  // 指向父节点
  this.return = null;
  // 指向第一个子节点
  this.child = null;
  // 指向兄弟节点
  this.sibling = null;
  // 个别如果没有兄弟节点的话是 0 当某个父节点下的子节点是数组类型的时候会给每个子节点一个 index
  this.index = 0;
  // 保留 ref 属性对象
  this.ref = null;
  
  // 新的 props 对象
  this.pendingProps = pendingProps;
  // 现有的 props 对象
  this.memoizedProps = null;
  // 保留更新对象的队列
  this.updateQueue = null;
  // 现有的 state 对象
  this.memoizedState = null;
  // 依赖对象
  this.dependencies = null;
    
  // 渲染形式
  // React 18 默认是 `ConcurrentMode`: 0b000001
  // packages/react-reconciler/src/ReactTypeOfMode.js 文件中定义
  this.mode = mode;

  // Effects
  // effect 的 Flag,表明以后的 effect 是 ` 替换 `/ ` 更新 ` / ` 删除 ` 等操作
  // packages/react-reconciler/src/ReactFiberFlags.js
  this.flags = NoFlags;
  // 子树的 Flag 合集
  this.subtreeFlags = NoFlags;
  // 须要删除的 fiber 节点
  this.deletions = null;
  
  // 更新渲染调度优先级相干
  // packages/react-reconciler/src/ReactFiberLane.old.js 文件中定义
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  
  // current 树和 workInprogress 树之间的互相援用
  // current 树就是以后的 Fiber 树
  // workInprogress 树 就是正在更新的 Fiber 树
  // 后续讲到组件更新会具体讲到
  this.alternate = null;

  if (enableProfilerTimer) {//。。。省略}
 }

总结一下:createContainer 办法通过 createFiberRoot 创立并返回 Fiber 根节点FiberRootNode 对象。同时该对象的 current 属性指向一个 Fiber HostRoot 节点

markContainerAsRoot 办法在容器 DOM 节点上增加一个属性 __reactContainer${randomKey},属性的值指向Fiber HostRoot 节点。以表明该 DOM 节点为以后 React 利用的容器节点。

listenToAllSupportedEvents 函数实现了 事件委托解决 的工作

packages/react-dom/src/events/DOMPluginEventSystem.js 文件中,listenToAllSupportedEvents 函数接管一个入参:容器 DOM 节点(也就是createRoot 函数的第一个参数)

大抵的原理是:React 18 把所有事件都委托到这个节点下面,一旦原生事件触发之后,这个节点会依据事件类型以及优先级,触发对应 fiber 节点上的事件回调函数。目前能够先理解一下 React 合成事件,后续章节讲到事件机制会具体解说

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {if (!(rootContainerElement: any)[listeningMarker]) {(rootContainerElement: any)[listeningMarker] = true;
    
    // allNativeEvents 是一个汇合,保留了 React 反对的所有事件
    // Set(81) {'abort', 'auxclick', 'cancel', 'canplay', 'canplaythrough', …}
    allNativeEvents.forEach(domEventName => {
       
      // 这里最重要的函数就是 `listenToNativeEvent`
      // 用于将事件绑定到容器的 DOM 节点
      // 上面会依据是否响应捕捉阶段分逻辑解决(可先疏忽)// selectionchange 事件也独自解决(可先疏忽)// listenToNativeEvent 事件外部调用 addTrappedEventListener 函数
      
      if (domEventName !== 'selectionchange') {if (!nonDelegatedEvents.has(domEventName)) {listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });
    //。。。省略 selectionchange 逻辑
  }
}

addTrappedEventListener 函数次要实现:依据事件获取对应的优先级,不同的优先级在 容器 DOM 节点 注册不同的事件回调函数

function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {

   // packages/react-reconciler/src/ReactEventPriorities.js 文件保留事件优先级的定义

   // createEventListenerWrapperWithPriority 逻辑:// 1. 调用 `getEventPriority` 函数实现从 ` 事件名 ` 到 ` 事件优先级 ` 的转化
   // 2. 依据 ` 事件优先级 ` eventPriority 匹配不同的回调函数:(dispatchDiscreteEvent,dispatchContinuousEvent,dispatchEvent)// 3. 返回事件回调函数,赋值给 listener
   
  var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the event will be


  var isPassiveListener = undefined;

  if (passiveBrowserEventsSupported) {// 逻辑省略}

  targetContainer =  targetContainer;
  var unsubscribeListener; 

  // 事件绑定逻辑:// 调用 addEventCaptureListener(WithPassiveFlag) / addEventBubbleListener((WithPassiveFlag)) 函数进行事件绑定,// 外部调用原生办法 dom.addEventListener,实现事件绑定
  if (isCapturePhaseListener) {if (isPassiveListener !== undefined) {unsubscribeListener = addEventCaptureListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
    } else {unsubscribeListener = addEventCaptureListener(targetContainer, domEventName, listener);
    }
  } else {if (isPassiveListener !== undefined) {unsubscribeListener = addEventBubbleListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
    } else {unsubscribeListener = addEventBubbleListener(targetContainer, domEventName, listener);
    }
  }
}

总结一下:listenToAllSupportedEvents 函数依据不同的事件类型,给 容器 DOM 节点 注册不同的回调函数,子组件的所有事件都由该节点进行散发和触发

返回 ReactDOMRoot 对象

实例化 ReactDOMRoot 对象,将 Fiber HostRoot 节点 传人构造函数中,保留在对象的 _internalRoot 属性

// ReactDOMRoot 构造函数
// 比较简单,不解释
function ReactDOMRoot(internalRoot: FiberRoot) {this._internalRoot = internalRoot;}

createRoot 函数最初返回 ReactDOMRoot 对象,实现整个函数的所有工作。接下来,调用ReactDOMRoot 对象的 render 办法进行渲染工作

render

render 办法在 packages/react-dom/src/client/ReactDOMRoot.js 文件中实现,入参是子组件,函数外部调用了 updateContainer 办法对子组件(App)进行渲染

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(children: ReactNodeList,): void {
  const root = this._internalRoot;
  if (root === null) {throw new Error('Cannot update an unmounted root.');
  }
  const container = root.containerInfo;
  // 重要步骤,重点剖析
  updateContainer(children, root, null, null);
};

updateContainer 函数在 packages/react-reconciler/src/ReactFiberReconciler.old.js 文件中定义,次要实现容器的调度工作

Lane 在 React 中用于示意工作的优先级,目前只须要有个大略的理解,后续会具体解说

schedule 是一个独立的任务调度模块,目前只用于 React 外部,很多 API 还处于 unstable 状态,后续有可能会提供给内部我的项目应用;这个模块也会在后续独自解说,敬请期待


// 删除一些烦扰逻辑之后的 `updateContainer` 函数

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  // 以后的 Fiber 树
  const current = container.current;
  // 以后事件工夫,调用 `now` 函数
  const eventTime = requestEventTime();
  // 获取以后更新的 lane(任务调度优先级)const lane = requestUpdateLane(current);

  // 获取上下文
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    // 将 FiberRootNode 的 context 属性指向 context
    container.context = context;
  } else {
    // 将 FiberRootNode 新的 context 属性指向 context
    container.pendingContext = context;
  }

  // 创立一个 ` 更新对象 `:update
  /* var update = {
    eventTime: eventTime, // 事件工夫
    lane: lane, // 调度优先级
    tag: UpdateState, // 标识是 update / delete / ForceUpdate / ...
    payload: null, // payload,保留 {element: React.element}
    callback: null, // 回调函数
    next: null // 指向下一个 update
  };
  */
  const update = createUpdate(eventTime, lane);
 
  // 设置更新对象的 paylaod 属性
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {update.callback = callback;}
  // 把 ` 更新对象 ` enqueue 到 ` 更新队列 `
  // 后续在将组件更新的时候会细讲,这块还是比拟重要的,目前能够大略理解
  enqueueUpdate(current, update, lane);
  
  // scheduleUpdateOnFiber 利用到 scheduler 这个包来进行任务调度
  // 通过将渲染办法 performConcurrentWorkOnRoot 注册到 scheduler 的调度机制中
  // scheduler 会依据工作优先级执行这个渲染办法,将 APP 组件最终渲染到页面上
  const root = scheduleUpdateOnFiber(current, lane, eventTime);
  if (root !== null) {entangleTransitions(root, current, lane);
  }

  return lane;
}

至此,整个 React 我的项目的初始化过程就实现了,为了保障本章内容足够简略,很多细节都还没有深刻解说。不过,置信读者读完本章后,对整个初始化的过程也有了肯定的理解。在后续的章节中,咱们将针对 React 我的项目的其余阶段进行深刻分析。同时也欢送各位读者能够在下方给我留言一起交换,共同进步

最初最初,心愿疫情可能早日完结,中国加油,世界加油!!!

正文完
 0