西安网站设计哪家公司好,一级a做爰片免费网站给我看看,推广链接跳转,网站建设一样注意什么引文
通过本文你将了解到
什么是虚拟DOM#xff1f;虚拟DOM有什么优势#xff1f;React的虚拟Dom是如何实现的#xff1f;React是如何将虚拟Dom转变为真实Dom#xff1f;
一、概念
虚拟DOM实际上是一种用来模拟DOM结构的javascript对象。当页面变化的时候通过一种算法来…引文
通过本文你将了解到
什么是虚拟DOM虚拟DOM有什么优势React的虚拟Dom是如何实现的React是如何将虚拟Dom转变为真实Dom
一、概念
虚拟DOM实际上是一种用来模拟DOM结构的javascript对象。当页面变化的时候通过一种算法来对比前后的虚拟DOM结构找到需要更改的地方然后再操作真实DOM就可以更新页面。
二、虚拟DOM有哪些优势
2.1 浏览器兼容性最佳
React中的虚拟DOM具有强大的兼容性。我们现在知道在React编译过程中React和babel将JSX转为JS对象也就是虚拟DOM之后通过render函数生成真实DOM。在编译虚拟DOM的过程中React还做了许多处理比如diff对比和兼容处理就是在这个阶段。
在JQuery中操作DOM十分便捷比如原生的input就有onchange事件但这都是在浏览器底层对DOM的处理不同的DOM只能触发关联给他的一些事件比如div标签本身没有onchange事件但是虚拟Dom就不同了虚拟Dom一方面模仿了原生Dom的行为其次在事件方面也做了合成事件与原生事件的映射关系比如
{onClick: [click],onChange: [blur, change, click, focus, input, keydown, keyup, selectionchange]
}React暴露给我们的合成事件其实在底层会关联到多个原生事件通过这种做法抹平了不同浏览器之间的api差异也带来了更强大的事件系统。
2.2 渲染机制的优化
我们知道React是state数据驱动视图只要state发生了改变那么render就会重新触发以达到更新UI层的效果。
但是在某些处理逻辑中某个state的值会多次发生变化比如在某个事件中短时间内多次setState这时React会进行多次渲染吗
答案是不会。 在 React 中state 的更新是异步的React 会在更新页面之前进行优化因此可能会把多次 setState() 调用合并为一次更新以提高性能。
当然如果我们是直接操作Dom那还有哪门子的异步和渲染等待当你append完一个子节点页面早渲染完了。所以虚拟Dom的对比提前以及setState的异步处理本质上也是为了尽可能少的操作DOM。
2.3 跨平台能力
因为 React 只是在 JavaScript 层面上操作虚拟 DOM所以可以在不同平台上使用相同的代码来渲染用户界面。
之所以加入虚拟Dom这个中间层除了解决部分性能问题加强兼容性之外还有个目的是将Dom的更新抽离成一个公共层别忘了React除了做页面引用外React还支持使用React Native创建端 App。
三、React中虚拟DOM的实现原理
下面以React源码为准看一下React的底层如何实现虚拟DOM。
在React中创建虚拟DOM的方法React.createElement下面这段代码摘除了dev环境的报错逻辑。
/*** 创建并返回给定类型的新ReactElement。* See https://reactjs.org/docs/react-api.html#createelement*/
function createElement(type, config, children) {let propName;// 创建一个全新的props对象const props {};let key null;let ref null;let self null;let source null;// 有传递自定义属性进来吗有的话就尝试获取ref与keyif (config ! null) {if (hasValidRef(config)) {ref config.ref;}if (hasValidKey(config)) {key config.key;}// 保存self和sourceself config.__self undefined ? null : config.__self;source config.__source undefined ? null : config.__source;// 剩下的属性都添加到一个新的props属性中。注意是config自身的属性for (propName in config) {if (hasOwnProperty.call(config, propName) !RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] config[propName];}}}// 处理子元素默认参数第二个之后都是子元素const childrenLength arguments.length - 2;// 如果子元素只有一个直接赋值if (childrenLength 1) {props.children children;} else if (childrenLength 1) {// 如果是多个转成数组再赋予给propsconst 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) {// 默认值只处理值不是undefined的属性if (props[propName] undefined) {props[propName] defaultProps[propName];}}}// 调用真正的React元素创建方法return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}代码看似很多其实逻辑非常清晰
处理参数对传进来的数据进行加工处理比如提取config参数处理props等通过ReactElement构造函数返回ReactNode对象
数据加工部分分为三步
第一步判断config有没有传不为null就做处理 判断ref、key__self、__source这些是否存在或者有效满足条件就分别赋值给前面新建的变量。遍历config并将config自身的属性依次赋值给前面新建props。 第二步处理子元素。默认从第三个参数开始都是子元素。 如果子元素只有一个直接赋值给props.children。如果子元素有多个转成数组后再赋值给props.children。 第三步处理默认属性defaultProps 一个纯粹的标签也可以理解成一个最最最基础的组件而组件支持 defaultProps所以这一步判断有没有defaultProps如果有同样遍历并将值不为undefined的部分都拷贝到props对象上。
逻辑抽离出来看起来并不难
我们在看一下ReactElement同样是删除了dev环境逻辑
const ReactElement function (type, key, ref, self, source, owner, props) {const element {// 这个标签允许我们将其标识为唯一的React Element$$typeof: REACT_ELEMENT_TYPE,// 元素的内置属性type: type,key: key,ref: ref,props: props,// 记录负责创建此元素的组件。_owner: owner,};return element;
};这里主要是将前面的一些数据生成一个element对象也就是虚拟DOM。
这里提一下REACT_ELEMENT_TYPE他的实现是
export const REACT_ELEMENT_TYPE Symbol.for(react.element);如果你自己实现了上面打印虚拟DOM的场景或许你有点印象。
在这里 $$typeof定义为Symbol(react.element)而Symbol一大特性就是标识唯一性即便两个看着一模一样的Symbol它们也不会相等。而React之所以这样做本质也是为了防止xss攻击防止外部伪造虚拟Dom结构。
至此我们又了解到了React底层是如何实现虚拟DOM这里留下一个问题我看到别人的文章中有提到但是我自己还没去复现。
React中虚拟Dom的是否允许修改或者添加新的属性
四、React中的虚拟DOM如何转变为真实DOM
一般来说使用React编写应用ReactDOM.render是我们触发的第一个函数。那么我们先从ReactDOM.render这个入口函数开始分析render的整个流程。 下面贴出来的源码也会把dev环境下的代码删掉我们只关注具体的逻辑 虚拟DOM会通过ReactDOM.render进行渲染成真实DOM
class P extends React.Component {render() {return (span classNamespanspanhello World!/span/span);}
}4.1 React render
export function render(element: React$Elementany,container: Container,callback: ?Function) {// 校验container是否合法invariant(isValidContainer(container),Target container is not a DOM element.,);// 调用 legacyRenderSubtreeIntoContainer 方法return legacyRenderSubtreeIntoContainer(null,element,container,false,callback,);
}可以看出 render方法实际上只对container进行节点类型的校验如果不是一个合法的Dom节点就会抛出错误我们只需要关注核心逻辑legacyRenderSubtreeIntoContainer()
4.2 legacyRenderSubtreeIntoContainer
legacyRenderSubtreeIntoContainer函数名的意思就是“组件子树继承渲染到容器中”其实就是把虚拟的Dom树渲染到真实的Dom容器中
function legacyRenderSubtreeIntoContainer(parentComponent: ?React$Componentany, any,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function,
) {
// root:FiberRootNode 是整个应用的根结点
// 绑定在真实DOM节点的_reactRootContainer属性上let root: RootType (container._reactRootContainer: any);let fiberRoot;
// 判断 根节点是否存在if (!root) {//如果不存在则说明是Initial mount 阶段调用函数生成rootNoderoot container._reactRootContainer legacyCreateRootFromDOMContainer(container,forceHydrate,);// 取出root内部的_internalRoot属性fiberRoot root._internalRoot;if (typeof callback function) {const originalCallback callback;// 封装 callback 回调callback function() {//通过fiberRoot找到当前对应的rootFiber//将rootFiber.child.stateNode作为callback中的this指向const instance getPublicRootInstance(fiberRoot);originalCallback.call(instance);};} // 初始化不允许批量处理,使用 unbatchedUpdates 调用 updateContainer()同步生成unbatchedUpdates(() {updateContainer(children, fiberRoot, parentComponent, callback);});} else {fiberRoot root._internalRoot;if (typeof callback function) {const originalCallback callback;callback function() {const instance getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// batchedUpdates 调用 updateContainer()updateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot);
}继续看一下getPublicRootInstance函数
export function getPublicRootInstance(container: OpaqueRoot,
): React$Componentany, any | PublicInstance | null {// 取出当前Fiber节点通过一些判断寻找FiberRootconst containerFiber container.current;// 是否存在子Fiber节点if (!containerFiber.child) {return null;}// 判断子Fiber节点tag类型switch (containerFiber.child.tag) {// 这里 HostComponent 5case HostComponent:return getPublicInstance(containerFiber.child.stateNode);default:return containerFiber.child.stateNode;}
}再梳理一下这个过程
root container._reactRootContainer如不存在表示是 Initial mount 阶段调用 legacyCreateRootFromDOMContainer 生成如存在表示是 update 阶段fiberRoot root._internalRoot从 root 上拿到内部 _internalRoot 属性封装 callback 回调通过 fiberRoot 找到其对应的 rootFiber然后将 rootFiber.child.stateNode 作为 callback 中的 this 指向调用 callbackmount 阶段需要尽快完成不允许批量更新使用的是legacy渲染模式使用 unbatchedUpdates 调用 updateContainer() update 阶段直接调用 updateContainer() 执行更新返回getPublicRootInstance(fiberRoot)返回公开的 Root 实例对象。
上面看似新建、更新两种情况都用的一个函数React故意将他们命名为一样其实new的时候还是走的new生成逻辑update走的update逻辑。
就像这里
export const updateContainer enableNewReconciler? updateContainer_new: updateContainer_old;FiberRoot和rootFiber的区别 首次执行ReactDOM.render会创建fiberRootNode源码中叫fiberRoot和rootFiber。fiberRootNode是整个应用的根节点绑定在真实DOM节点的_reactRootContainer属性上rootFiber是当前所在组件树的根节点,rootFiber在每次重新渲染的时候会重新构建。 4.3 updateContainer
这里必须关注一下updateContainer()走到这里就进入了一个非常关键的处理 来看一下updateContainer的源码
function createContainer(containerInfo: Container,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}export function updateContainer(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Componentany, any,callback: ?Function,
): Lane {const current container.current;// 获取更新任务的触发时间可以理解为返回的时间越小则执行的优先级越高const eventTime requestEventTime();// lane 中文含义是车道 在 React 中lane 是一个number值使用 32 个比特位表示32个“车道”。// 通过一系列处理确定任务真正的优先级并申请相关的车道const lane requestUpdateLane(current);if (enableSchedulingProfiler) {markRenderScheduled(lane);}const context getContextForSubtree(parentComponent);if (container.context null) {container.context context;} else {container.pendingContext context;}//返回一个包装好的任务 update 存放一些数据 // const update: Update* {// eventTime: number,// lane: Lane,// tag: 0 | 1 | 2 | 3,// payload: any,// callback: (() mixed) | null,// next: UpdateState | null,// };const update createUpdate(eventTime, lane);update.payload {element};callback callback undefined ? null : callback;// 将需要更新的任务对象关联进 Fiber 任务队列形成环状链表enqueueUpdate(current, update);// 进入Fiber的协调、调度scheduleUpdateOnFiber(current, lane, eventTime);return lane;
}这一部分React做了什么
他为传进来的Fiber申请了lane确定了优先级生成一个更新的任务update类型的数据将需要更新的任务关联进了Fiber的任务队列并且形成了环状链表进入Fiber的协调调度函数scheduleUpdateOnFiber安排Fiber节点挂载。
在后续scheduleUpdateOnFiber的处理中会调用checkForNestedUpdates()他处理任务更新的嵌套层数如果嵌套层数过大 50 就会认为是无效更新则会抛出异常。
之后便根据markUpdateLaneFromFiberToRoot对当前的fiber树自底向上的递归fiber的lane根据lane做二进制比较或者位运算处理ensureRootIsScheduled里确定调度更新的模式有performSyncWorkOnRoot或performConcurrentWorkOnRoot方法。不同的调用取决于本次更新是同步更新还是异步更新。
updateContainer 不管什么模式都会走 performSyncWorkOnRoot这个函数的核心功能分别是 这个函数而这个函数三个核心处理过程分别是
beginWorkcompleteUnitOfWorkcommitRoot 在 render 阶段会通过遍历的方式向下调和向上归并从而创建一颗完整的Fiber Tree调和的过程也就是 beginWork 归并就是 completeWork 过程 4.4 performSyncWorkOnRoot 函数的三大核心处理
(1) beginWork
我们直接跳过一些逻辑看一个比较重要的方法beginWork
这段代码太长我们只看部分逻辑
function beginWork( current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,) {// 删除部分无影响的代码workInProgress.lanes NoLanes;//这里有一些判断FiberProps是否可以复用的逻辑然后做一些处理switch (workInProgress.tag) {// 模糊定义的组件case IndeterminateComponent:{return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);}// 函数组件case FunctionComponent:{var _Component workInProgress.type;var unresolvedProps workInProgress.pendingProps;var resolvedProps workInProgress.elementType _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);}// class组件case ClassComponent:{var _Component2 workInProgress.type;var _unresolvedProps workInProgress.pendingProps;var _resolvedProps workInProgress.elementType _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);}case HostRoot:return updateHostRoot(current, workInProgress, renderLanes);case ...:return ...}
}这里beginWork做的很重要的一步就是根据render传进来组件类型的不同来选择不同的组件更新的方法。
我们可以根据 current 是否为 null 来判断当前组件是处于 update 阶段还是 **mount **阶段 因此beginWork 的工作其实可以分成两部分
mount 时会根据 Fiber.tag 的不同执行不同类型的创建子 Fiber 节点的程序update 时会根据一定的条件复用 current 节点这样可以通过 clone current.child 来作为 workInProgress.child 而不需要重新创建
比如我们最初定义了一个Class组件P这里就会进入**updateClassComponent()**来更新组件
function updateClassComponent(current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes,
) {// 删除了添加context部分的逻辑// 获取组件实例var instance workInProgress.stateNode;var shouldUpdate;// 如果没有实例那就得创建实例if (instance null) {if (current ! null) {current.alternate null;workInProgress.alternate null;workInProgress.flags | Placement;}// 这里new Class创建组件实例constructClassInstance(workInProgress, Component, nextProps);// 挂载组件实例mountClassInstance(workInProgress, Component, nextProps, renderLanes);shouldUpdate true;} else if (current null) {shouldUpdate resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes);} else {shouldUpdate updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);}// Class组件的收尾工作var nextUnitOfWork finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes);
}在看这段代码前我们自己也可以提前想象下这个过程比如Class组件你一定是得new才能得到一个实例只有拿到实例后才能调用其render方法拿到其虚拟Dom结构之后再根据结构创建真实Dom添加属性最后加入到页面。
所以在updateClassComponent中首先会对组件做context相关的处理这部分代码我删掉了其余判断当前组件是否有实例如果有就去更新实例如果没有那就创建实例所以我们聚焦到constructClassInstance与mountClassInstance、finishClassComponent三个方法看命名就能猜到前者一定是创造实例后者是应该是挂载实例前的一些处理先看第一个方法
function constructClassInstance(workInProgress, ctor, props) {// 删除了对组件context进一步加工的逻辑// ....// 这里创建了组件实例// 验证了前面的推测这里new了我们的组件并且传递了当前组件的props以及前面代码加工的contextvar instance new ctor(props, context);var state workInProgress.memoizedState instance.state ! null instance.state ! undefined ? instance.state : null;adoptClassInstance(workInProgress, instance);// 删除了对于组件生命周期钩子函数的处理比如很多即将被废弃的钩子在这里都会被添加 UNSAFE_ 前缀//.....return instance;
}果然这里通过new ctor(props, context)创建了组件实例。
下面看mountClassInstance()
function mountClassInstance(workInProgress, ctor, newProps, renderLanes) {// 此方法主要是对constructClassInstance创建的实例进行数据组装为其赋予props,state等一系列属性var instance workInProgress.stateNode;instance.props newProps;instance.state workInProgress.memoizedState;instance.refs emptyRefsObject;initializeUpdateQueue(workInProgress);// 删除了部分特殊情况下对于instance的特殊处理逻辑
}虽然命名是挂载但其实离真正的挂载还远得很本方法其实是为constructClassInstance创建的组件实例做数据加工为其赋予props state等一系列属性。
在上文代码中其实还有个finishClassComponent方法此方法在组件自身都准备完善后调用我们期待已久的render方法处理就在里面
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {var instance workInProgress.stateNode;ReactCurrentOwner$1.current workInProgress;var nextChildren;if (didCaptureError typeof Component.getDerivedStateFromError ! function) {// ...} else {{setIsRendering(true);// 关注点在这通过调用组件实例的render方法得到内部的元素nextChildren instance.render();setIsRendering(false);}} if (current ! null didCaptureError) {forceUnmountCurrentAndReconcile(current,workInProgress,nextChildren,renderLanes,);} else {//reconcileChildren 做的事情就是 react 的另一核心之一 —— diff 过程reconcileChildren(current, workInProgress, nextChildren, renderLanes);}workInProgress.memoizedState instance.state;return workInProgress.child;
}(2) completeWork
当 workInProgress 为 null 时也就是当前任务的 fiber 树遍历完之后就进入到了 completeUnitOfWork 函数。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function completeUnitOfWork(unitOfWork: Fiber): void {let completedWork unitOfWork;do {// ... // 对节点进行completeWork生成DOM更新props绑定事件next completeWork(current, completedWork, subtreeRenderLanes);if (returnFiber ! null (returnFiber.flags Incomplete) NoFlags) {// 将当前节点的 effectList 并入到父节点的 effectListif (returnFiber.firstEffect null) {returnFiber.firstEffect completedWork.firstEffect;}if (completedWork.lastEffect ! null) {if (returnFiber.lastEffect ! null) {returnFiber.lastEffect.nextEffect completedWork.firstEffect;}returnFiber.lastEffect completedWork.lastEffect;}// 将自身添加到 effectList 链添加时跳过 NoWork 和 PerformedWork的 flags因为真正的 commit 时用不到const flags completedWork.flags;if (flags PerformedWork) {if (returnFiber.lastEffect ! null) {returnFiber.lastEffect.nextEffect completedWork;} else {returnFiber.firstEffect completedWork;}returnFiber.lastEffect completedWork;}}} while (completedWork ! null);// ...
}
经过了 beginWork 操作workInProgress 节点已经被打上了 flags 副作用标签。completeUnitOfWork 方法中主要是逐层收集 effects 链最终收集到root上供接下来的 commit 阶段使用。
到这里我们可以理解例子P组件虚拟Dom都准备完毕现在要做的是对于虚拟Dom这种最基础的组件做转成真实Dom的操作见如下代码
function completeWork(current, workInProgress, renderLanes) {var newProps workInProgress.pendingProps;// 根据tag类型做不同的处理switch (workInProgress.tag) {// 标签类的基础组件走这条路case HostComponent:{popHostContext(workInProgress);var rootContainerInstance getRootHostContainer();var type workInProgress.type;if (current ! null workInProgress.stateNode ! null) {// ...} else {// ...} else {// 关注点1创建虚拟Dom的实例var instance createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);appendAllChildren(instance, workInProgress, false, false);workInProgress.stateNode instance; // Certain renderers require commit-time effects for initial mount.// 关注点2初始化实例的子元素if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {markUpdate(workInProgress);}}}}}
}createInstance调用createElement方法创建真正的DOM实例React会根据你的标签类型来决定怎么创建Dom。比如我们的span很显然就是通过**ownerDocument.createElement(type)**创建如下图 创建完成后此时的span节点还是一个啥都没有的空span所以后续来到finalizeInitialChildren方法这里开始对创建的span节点的children子元素进一步加工再通过里面的一些函数做一些对节点的加工处理比如设置节点的标签样式等等。
那么到这里其实我们的组件P已经准备完毕包括真实Dom也都创建好了就等插入到页面了那这些Dom什么时候插入到页面的呢
(3) commit
completeUnitOfWork结束后render 阶段便结束了后面就到了commit阶段。
其实到这里可以算是render阶段的完成这里在内存中构建 workInProgress Fiber 树的所有工作都已经完成这其中包括了对 Fiber 节点的 update、diff、flags 标记、subtreeFlagseffectList 的收集等一系列操作在 completeWork阶段形成了 effectList 链表连接所有需要被更新的节点。
下面为了将这些需要更新的节点应用到真实 DOM 上却不需要遍历整棵树在 commit 阶段会通过遍历这条 EffectList 链表执行对应的操作来完成对真实 DOM 的更新。
这个阶段我们直接看他是怎么把真实DOM节点插入到容器中的直接定位到insertOrAppendPlacementNodeIntoContainer方法直译过来就是将节点插入或者追加到容器节点中
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {var tag node.tag;var isHost tag HostComponent || tag HostText;if (isHost || enableFundamentalAPI ) {var stateNode isHost ? node.stateNode : node.stateNode.instance;if (before) {// 在容器节点前插入insertInContainerBefore(parent, stateNode, before);} else {// 在容器节点后追加appendChildToContainer(parent, stateNode);}} else if (tag HostPortal) ; else {var child node.child;// 只要子节点不为null继续递归调用if (child ! null) {insertOrAppendPlacementNodeIntoContainer(child, before, parent);var sibling child.sibling;// 只要兄弟节点不为null继续递归调用while (sibling ! null) {insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);sibling sibling.sibling;}}}
}这里React主要做了两件事情
如果是原生 DOM 节点调用 insertInContainerBefore 或 appendChildToContainer 来在相应的位置插入 DOM 节点如果不是原生 DOM 节点会对当前 Fiber 节点的所有子 Fiber 节点调用 insertOrAppendPlacementNodeIntoContainer对自身进行遍历直到找到 DOM 节点然后插入
我们再看一看appendChildToContainer的实现
function appendChildToContainer(container, child) {var parentNode;if (container.nodeType COMMENT_NODE) {parentNode container.parentNode;parentNode.insertBefore(child, container);} else {parentNode container;// 将子节点插入到父节点中parentNode.appendChild(child);var reactRootContainer container._reactRootContainer;if ((reactRootContainer null || reactRootContainer undefined) parentNode.onclick null) {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(parentNode);}
}结合我们前面自己写的例子
class P extends React.Component {render() {return (span classNamespanspanhello World!/span/span);}
}由于我们定义的组件非常简单P组件只有一个span标签所以这里的parentNode其实就是容器根节点当执行完parentNode.appendChild(child) 可以看到页面就出现了“hello World!”了。
6.5 小结
至此组件的虚拟Dom生成真实Dom的创建加工以及渲染全部执行完毕。
上面有多次提到fiber节点其实我们在创建完真实Dom后它还是会被加工成一个fiber节点而此节点中通过child可以访问到自己的子节点通过sibling获取自己的兄弟节点最后通过return属性获取自己的父节点通过这些属性为构建Dom树提供了支撑至于fiber是如何实现的这里就不多做说明啦