ReactDOM.render源码解析-1
初步看了react-dom这个包的一些源码,发现其比react包要复杂得多,react包中基本不存在跨包调用的情况,他所做的也仅仅是定义了ReactElement对象,封装了对ReactElement的基本操作,而react-dom包存在复杂的函数调用。本文将对ReactDOM.render源码做一个初步解析。
文章中如有不当之处,欢迎交流指点。react版本16.8.2
。在源码添加的注释在githubreact-source-learn。
前言
使用react时常常写类似下面的代码:
import ReactDOM from 'react-dom'; ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
代码1
这里导入的ReactDOM就是packages/react-dom/client/ReactDOM.js中所导出的对象。从文档可见ReactDOM对象有如下几个方法:(ps:从源码看其实还有很多其他方法)
- render()
- hydrate()
- unmountComponentAtNode()
- findDOMNode()
- createPortal()
本文只介绍render()方法
代码分析
render方法定义如下:
render( element: React$Element<any>, container: DOMContainer, callback: ?Function, ) { invariant( // 1 isValidContainer(container), 'Target container is not a DOM element.', ); if (__DEV__) { warningWithoutStack( !container._reactHasBeenPassedToCreateRootDEV, 'You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOM.%s(). This is not supported. ' + 'Did you mean to call root.render(element)?', enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', ); } // 2 return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); },
代码2
render方法接收两个必选参数可一个可选参数,结合代码1的调用可知,element是一个ReactElement对象, container是一个dom节点,callback在上面的代码1并没有指定,他是一个可选函数。
这个render方法做的事情比较简单,一是校验container参数,二是调用legacyRenderSubtreeIntoContainer方法并返回。
接下来是legacyRenderSubtreeIntoContainer
// 删除了第一次调ReactDOM.render不会走的分支 function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // ReactDOM.render 是null children: ReactNodeList, // 是一个ReactElement , ReactDOM.render是第一个参数 container: DOMContainer, // 是一个dom节点, ReactDOM.render是第二个参数 forceHydrate: boolean, // ReactDOM.render 是false callback: ?Function, // ReactDOM.render 是 第三个参数 ) { if (__DEV__) { topLevelUpdateWarnings(container); } // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. // 根据type知道, Root type是个对象,包含 // render方法 // unmount方法 // legacy_renderSubtreeIntoContainer 方法 // createBatch 方法 // _internalRoot属性 let root: Root = (container._reactRootContainer: any); if (!root) { // ReactDOM.render调用时走这里 // Initial mount // 调用 legacyCreateRootFromDOMContainer 拿 Root root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, // ReactDOM.render是false ); // 在callback加参数 if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. // 这个是packages/react-reconciler/ReactFiberScheduler.js中的方法 // TOLEARN: 这个里边应该是一些调度过程, 后续再看 unbatchedUpdates(() => { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { // ReactDOM.render方法走这里 // 这里的root.render 返回的是一个叫Work的东西, TOLEARN,这个Work后面再做了解 root.render(children, callback); } }); } // getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法 // 关于他是返回的一个什么东西, 后面再看, 总之他是我ReactDOM.render方法回调函数的一个参数, // 也是返回值 return getPublicRootInstance(root._internalRoot); }
legacyRenderSubtreeIntoContainer在第一次render时做了如下事情:
- 调用legacyCreateRootFromDOMContainer拿到一个ReactRoot的实例
- 在callback中注入一个参数instance
- 调用unbatchedUpdates开始一轮调度过程,这个是猜的
- 返回instance
关于instance的获取是调用的getPublicRootInstance,getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法,后面再研究
关于unbatchedUpdates, 这个东西是这个是packages/react-reconciler/ReactFiberScheduler.js中的方法,简单看了看还没太明白
接下来看一下ReactRoot实例的获取
// 删除了__DEV__分支的代码 // 若需要清理container的子节点,清理, 然后new ReactRoot并返回 function legacyCreateRootFromDOMContainer( container: DOMContainer, // dom节点 forceHydrate: boolean, // false render ): Root { // 是否不需要清理container的子元素, 第一次render是false, 即需要清理 const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 第一次调用时为false // First clear any existing content. if (!shouldHydrate) { // 第一次render走这里 let warned = false; let rootSibling; // 这里将container的子元素都清理掉了 while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } // Legacy roots are not async by default. const isConcurrent = false; return new ReactRoot(container, isConcurrent, shouldHydrate); }
这个方法比较简单,在第一次调用ReactDOM.render时,shouldHydrate会是false,所以会走到if (!shouldHydrate) 分支里,将container节点的所有子节点都清理掉,最后是new 了一个ReactRoot作为返回值,关于ReactRoot等内容将放到后面的文章分析。
小结
以上是ReactDOM.render的函数调用示意图。
- 首先在render方法中校验container参数是否合法,然后调用legacyRenderSubtreeIntoContainer
- 在legacyRenderSubtreeIntoContainer中, 调用legacyCreateRootFromDOMContainer拿到了ReactRoot的一个实例,调用getPublicRootInstance拿到了instance,用于注入到callback,和作为返回值,调用unbatchedUpdates开始调度过程
- 在legacyCreateRootFromDOMContainer中,首先清理了container中的所有子节点,然后new了一个ReactRoot并返回
TODO
- unbatchedUpdates是如何调度的
- ReactRoot是一个什么样的类
- dom的操作是在哪里