React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom
一、ReactDOM.render 都干啥了我们在写react的时候,最后一步肯定是 ReactDOM.render( <div> <Home name="home"/> </div> ,document.getElementById(‘app‘) ); ? 我们上面得知jsx被解析成了虚拟dom对象,我们把一个对象和一个dom传入render方法就得到了我们的页面,好神奇呀,我们开始撸到render方法: const ReactDOM: Object = { render( element: React$Element<any>,// react组件对象 container: DOMContainer,// 就是id为app的那个dom callback: ?Function,// callback 没有 ) { return legacyRenderSubtreeIntoContainer( null,element,container,false,callback,); } } 抛开typeScript那些恶心的类型限定不谈,我们发现render的实质就是调用并返回? ?legacyRenderSubtreeIntoContainer? ?这个函数执行后的结果,你看这个函数的命名: legacy : 遗产? +??render: 渲染? +??subtree: 子树? +??into: 到 +??container: 容器 爱几把咋翻译咋翻译,大致意思就是说把 虚拟的dom树渲染到真实的dom容器中。此函数应当荣当 核心函数 宝座 ? 二、legacyRenderSubtreeIntoContainer 又干了啥?还是撸到丫的源码: ? function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any,any>,// null children: ReactNodeList,// element 虚拟dom树 container: DOMContainer,// html中的dom根对象 forceHydrate: boolean,// false 服务器端渲染标识 callback: ?Function,// 回调函数 没有 ) { // 对container进行校验 invariant( isValidContainer(container),‘Target container is not a DOM element.‘,); // 取root对象,一般如果非服务器端渲染这个root是不存在的 let root: Root = (container._reactRootContainer: any); // 进入浏览器端渲染流程 if (!root) { // 人工制造root,附加了一堆fiber的东西到_reactRootContainer root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container,forceHydrate,); if (typeof callback === ‘function‘) { const originalCallback = callback; callback = function() { // 该变callback的this为 instance const instance = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } DOMRenderer.unbatchedUpdates(() => { if (parentComponent != null) { // 向真实dom中挂载虚拟dom root.legacy_renderSubtreeIntoContainer( parentComponent,children,); } else { // 多么直白的语义 root.render(children,callback); } }); } else { // 还是先整一下callback if (typeof callback === ‘function‘) { const originalCallback = callback; callback = function() { const instance = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // 还是上面那一套 if (parentComponent != null) { // 向root中挂载dom root.legacy_renderSubtreeIntoContainer( parentComponent,); } else { root.render(children,callback); } } // 返回container 中的dom return DOMRenderer.getPublicRootInstance(root._internalRoot); } 通过看这个核心函数的代码,发现它其中有3个谜团: 1. _reactRootContainer 的制造:legacyCreateRootFromDOMContainer ? ?这个函数会制造一个对象挂载到真实的dom根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上 2. DOMRenderer.unbatchedUpdates ? ?它的回调执行了挂载dom结构的方法 3.? root.legacy_renderSubtreeIntoContainer 和 root.render ? ?如果有parentComponent 这个东西,就执行root.render 否则?root.legacy_renderSubtreeIntoContainer ? 三、跟进谜团1.root的制造找到?legacyCreateRootFromDOMContainer 函数: 1 function legacyCreateRootFromDOMContainer( 2 container: DOMContainer, 3 forceHydrate: boolean,// false 4 ): Root { 5 const shouldHydrate = 6 forceHydrate || shouldHydrateDueToLegacyHeuristic(container); 7 // 是否需要服务器端渲染 8 if (!shouldHydrate) { 9 let warned = false; 10 let rootSibling; 11 while ((rootSibling = container.lastChild)) { 12 if (__DEV__) { 13 ... 14 } 15 // 将dom根节点清空 16 container.removeChild(rootSibling); 17 } 18 } 19 if (__DEV__) { 20 ... 21 } 22 const isAsync = false; 23 return new ReactRoot(container,isAsync,shouldHydrate); 24 } 我们发现实际上该函数做的只是在非ssr的情况下,将dom根节点清空,然后返回一个new ReactRoot(...) 那么重点又跑到了ReactRoot中: 1 // 构造函数 2 function ReactRoot( 3 container: Container,// 被清空的dom根节点 4 isAsync: boolean,// false 5 hydrate: boolean // false 6 ) { 7 // 追查之后发现:createFiberRoot(containerInfo,hydrate); 8 // root 实际上就和fiber有了联系 9 const root = DOMRenderer.createContainer(container,hydrate); 10 this._internalRoot = root; 11 } 12 13 14 // 原型方法 15 16 // 渲染 17 ReactRoot.prototype.render = function( 18 children: ReactNodeList,19 callback: ?() => mixed,20 ): Work { 21 const root = this._internalRoot; 22 const work = new ReactWork(); 23 callback = callback === undefined ? null : callback; 24 25 if (callback !== null) { 26 work.then(callback); 27 } 28 DOMRenderer.updateContainer(children,root,null,work._onCommit); 29 return work; 30 }; 31 32 // 销毁组件 33 ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work { 34 ... 35 }; 36 37 38 ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function( 39 parentComponent: ?React$Component<any,any>,40 children: ReactNodeList,41 callback: ?() => mixed,42 ): Work { 43 const root = this._internalRoot; 44 const work = new ReactWork(); 45 callback = callback === undefined ? null : callback; 46 if (callback !== null) { 47 work.then(callback); 48 } 49 DOMRenderer.updateContainer(children,parentComponent,work._onCommit); 50 return work; 51 }; 52 53 ReactRoot.prototype.createBatch = function(): Batch { 54 ..... 55 }; 通过以上代码我们就了解到root到底是个啥玩意儿,这个root有render等方法外,同时还附加了一个和fiber相关的??_internalRoot属性。 由此可知,不管是root.render 还是 root.legacy_renderSubtreeIntoContainer? 都会去执行??DOMRenderer.updateContainer方法,无非就是传入的参数时:第三个参数传什么 而已。 2.DOMRenderer.unbatchedUpdates干了什么1 // 正在批量更新标识 2 let isBatchingUpdates: boolean = false; 3 // 未批量更新标识 4 let isUnbatchingUpdates: boolean = false; 5 // 非批量更新操作 6 function unbatchedUpdates<A,R>(fn: (a: A) => R,a: A): R { 7 // 如果正在批量更新 8 if (isBatchingUpdates && !isUnbatchingUpdates) { 9 // 未批量更新设为true 10 isUnbatchingUpdates = true; 11 try { 12 // 运行入参函数且返回执行结果 13 return fn(a); 14 } finally { 15 // 仍旧将未批量更新设为false 16 isUnbatchingUpdates = false; 17 } 18 } 19 // 不管是否在批量更新流程中,都执行入参函数 20 return fn(a); 21 } ? 记住这里两个十分重要的标识:isBatchingUpdates? 和??isUnbatchingUpdates? ? 初始值都是false ?由此可知 unbatchedUpdates 无论如何都会执行入参函数,无非在批量更新的时候多一些流程控制。这里留坑 ? 3.?root.legacy_renderSubtreeIntoContainer 和 root.render 在弄什么?通过上面的层层扒皮,无论怎样判断,最终都会到以上两个方法中,而这两个方法的核心就是?DOMRenderer.updateContainer,无非就是传不传父组件而已 传入的参数有: 1:虚拟dom对象树? ?2:之前造出来的root中和fiber相关的_internalRoot? 3.父组件(null 或 父组件)? 4.回调函数 1 export function updateContainer( 2 element: ReactNodeList,// 虚拟dom对象 3 container: OpaqueRoot,// 被制造出来的fiber root 4 parentComponent: ?React$Component<any,// null 5 callback: ?Function,//没传 6 ): ExpirationTime { 7 // 还记得虚拟dom对象 8 const current = container.current; 9 const currentTime = requestCurrentTime(); 10 const expirationTime = computeExpirationForFiber(currentTime,current); // 计算优先级 11 return updateContainerAtExpirationTime( 12 element, 13 container, 14 parentComponent, 15 expirationTime, 16 callback, 17 ); 18 } 19 // 剥皮 20 21 // 根据渲染优先级更新dom 22 export function updateContainerAtExpirationTime( 23 element: ReactNodeList,// 虚拟dom对象 24 container: OpaqueRoot,// 和fiber相关的_internalRoot 25 parentComponent: ?React$Component<any,// 可有可无 26 expirationTime: ExpirationTime,// 计算出来的渲染优先级 27 callback: ?Function,// 回调函数,没有 28 ) { 29 const current = container.current; 30 31 if (__DEV__) { 32 ... 33 } 34 // 获取到父组件内容 35 const context = getContextForSubtree(parentComponent); 36 // 赋值操作,不知道干啥用 37 if (container.context === null) { 38 container.context = context; 39 } else { 40 container.pendingContext = context; 41 } 42 // 又到了下一站:schedule:安排,Root: 根,Update:更新 43 return scheduleRootUpdate(current,expirationTime,callback); 44 } 45 // 剥皮 46 47 // 安排根节点更新 48 function scheduleRootUpdate( 49 current: Fiber,// fiber对象 50 element: ReactNodeList,// 虚拟dom树 51 expirationTime: ExpirationTime,// 更新优先级 52 callback: ?Function,// 回调 53 ) { 54 if (__DEV__) { 55 ... 56 } 57 /* 58 export const UpdateState = 0; 59 export function createUpdate(expirationTime: ExpirationTime): Update<*> { 60 return { 61 expirationTime: expirationTime, 62 63 tag: UpdateState, 64 payload: null, 65 callback: null, 66 67 next: null, 68 nextEffect: null, 69 }; 70 } 71 */ 72 73 // 返回一个包含以上属性的update对象 74 const update = createUpdate(expirationTime); 75 // 将虚拟dom树放入payload 76 update.payload = {element}; 77 78 callback = callback === undefined ? null : callback; 79 if (callback !== null) { 80 warningWithoutStack( 81 typeof callback === ‘function‘, 82 ‘render(...): Expected the last optional `callback` argument to be a ‘ + 83 ‘function. Instead received: %s.‘, 84 callback, 85 ); 86 update.callback = callback; 87 } 88 // 开始加入更新队列了,又得剥皮 89 enqueueUpdate(current,update); 90 // 91 scheduleWork(current,expirationTime); 92 return expirationTime; 93 } 94 95 96 // 更新队列 97 // 核心update 98 export function enqueueUpdate<State>( 99 fiber: Fiber,100 update: Update<State> // 上文那个update对象 101 ) { 102 // 根据fiber的指示进行更新 103 ... 104 } 根据一系列的剥皮,最终指向了enqueueUpdate 这个函数,而这个函数和fiber是紧密耦合的,fiber是一个棘手的问题,不理解fiber就无法弄清虚拟dom如何更新到真实dom中。 预知fiber如何,且听后续分晓!!! 四、本次的坑有以下几个:1.?_internalRoot 如何制造出来的,丫有什么作用,为什么后面的函数参数都传它 2. enqueueUpdate 跟fiber的关系还不清不楚 3. expirationTime 是干什么的,它的这个优先级有什么用呢? ? 下期解答! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |