哈喽大家好,良久没有跟大家以技术文章的模式见面了。最近自己在应用 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
对象,再通过 root
的 render
办法将 App
这个组件渲染到网页上
createRoot
咱们先看 createRoot
这个办法具体做了什么事件。这个办法来自 react-dom
这个包。咱们能够在源码中 packages / react-dom / src / client / ReactDOMRoot.js
中找到 createRoot
的具体实现(后面在 ReactDOM.js 做了一些对于环境的条件判断,可先疏忽)
createRoot
函数有两个参数 container
和 options
,其中 options
是可选参数,本章为了简略起见先不探讨;
该函数大略实现的性能就是:
- 创立容器对象
FiberRootNode
- 事件委托解决
- 依据
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 我的项目的其余阶段进行深刻分析。同时也欢送各位读者能够在下方给我留言一起交换,共同进步
最初最初,心愿疫情可能早日完结,中国加油,世界加油 !!!