legacyRenderSubtreeIntoContainer
在
ReactDOM.render方法中,本身没有太多逻辑,只是最后return的是legacyRenderSubtreeIntoContainer(null, element, container, false, callback);;因此我在legacyRenderSubtreeIntoContainer函数中debug;
legacyRenderSubtreeIntoContainer接收以下几个参数
parentComponent:父组件(React Component),通过ReactDOM.render调用的时候传的null,在编译后的代码中找到调用唯一传了parentComponent参数的是unstable_renderSubtreeIntoContainer,因此先跳过,后期遇到在详细看。
children: ReactDOM.render()第一个参数,也就是render里的ReactElement对象; 在下图中就是<h1>Hello World!</h1>
container:根节点(容器),如上图中document.getElementById('container')就是;forceHydrate:true 为服务端渲染,false为客户端渲染,通过ReactDOM.render方法渲染的是客户端渲染,为false(服务端渲染先不管,防止分散学习的思路)。callback:组件渲染完成后的回调函数,应该不常用,我上面children中的截图里面添加了该函数,组件渲染完成后会在控制台输出“渲染dom结束”;
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate:true 为 服务端渲染,false为客户端渲染,我们研究的是客户端渲染: boolean,
callback: ?Function,
)
打印出legacyRenderSubtreeIntoContainer中通过render方法调用传入的参数;
可以看到parentComponent,container,forceHydrate,callback都可以理解,但是children的输入内容和<h1>Hello World!</h1>不太一样啊,是在哪里处理的?
还记得隔壁的babel吗? 点击查看babel去在线体验
这里是不是回想到了什么,如果之前仔细查看过官网的同学都知道,Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,因此上面legacyRenderSubtreeIntoContainer中打印的children实际上是通过React.createElement()处理过的(React.createElement先不研究,后期进行不下去再看);
legacyRenderSubtreeIntoContainer中调用的那些函数
源码(区分了环境):
编译后:
topLevelUpdateWarnings:判断container容器是不是可以用的,里面各种error报错的,都是严重的错误
warnOnInvalidCallback: 接收两个参数,第一个是回调方法,第二个是一个标志函数的名字,这里render应该就是在报错的时候告诉我们是在render的方法中出现的错误。warnOnInvalidCallback$1(callback === undefined ? null : callback, 'render');
可以看到这个方法也很简单,只是判断了下如果render函数的回调存在的情况下是不是函数,如果不是函数,就会通过console对象输出error错误信息,error中的两个%s分别是后面callerName和callback的占位符(console对象使用详见www.runoob.com/w3cnote/jav…
如果把render函数回调改下:
ReactDOM.render(
<h1>Hello World!{<Test />}</h1>,
document.getElementById('container'),123
);
本来是回调函数的参数改为了“123”,此时“warnOnInvalidCallback$1”就会报错:
container._reactRootContainer
接下来,直接定义了root 和fiberRoot 变量,但是第一次调用render 方法时,这两个变量都还没有赋值,此时都是空的,因此会运行if(!root){} 代码块:
在给root 赋值时,调用了legacyCreateRootFromDOMContainer 方法中调用了 shouldHydrateDueToLegacyHeuristic 方法:
function shouldHydrateDueToLegacyHeuristic(container) {
var rootElement = getReactRootElementInContainer(container);
return !!(rootElement && rootElement.nodeType === ELEMENT_NODE && rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME));
}
这个方法用来判断是不是服务端渲染(查资料得知,如果是服务端渲染,会在根节点添加data-reactroot 属性),因为客户端调用render方法的时候传入的用来判断是不是服务端渲染的参数写死了是false ,如下图:
问了度娘后大概理解的是,render方法在16版本中还是可以复用ssr的 HTML 结构,不能只通过hydrate判断是否是ssr,因此shouldHydrateDueToLegacyHeuristic 方法中对当前容器做了是否有data-reactroot 的判断:
如果 shouldHydrate 是false,说明不是服务端渲染,就会执行下面代码,循环移除container中的lastChild,但是其中还有个判断:
if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {}
如果当前要移除的是dom元素,并且已经被服务端渲染的时候标记过(有data-reactroot属性),代码就会认为你操作有误,不应该去移除服务端渲染的html。
在接下来,就是判断服务端渲染的时候,是不是用了render方法,同时给你个警告,Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v18 。(各位耗子尾汁)
{
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
warn('render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v18. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.');
}
}
警告完后,最终调用了createLegacyRoot方法,将当前根节点container和是否是服务端渲染的参数hydrate传入
return createLegacyRoot(container, shouldHydrate ? {
hydrate: true
} : undefined);
createLegacyRoot方法通过ReactDOMBlockingRoot构造函数重新实例化:
var LegacyRoot = 0;
function createLegacyRoot(container, options) {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}
createRootImpl
该方法最终返回了处理完成后的root:
function createRootImpl(container, tag, options) {
// Tag is either LegacyRoot or Concurrent Root
var hydrate = options != null && options.hydrate === true;
var hydrationCallbacks = options != null && options.hydrationOptions || null;
var mutableSources = options != null && options.hydrationOptions != null && options.hydrationOptions.mutableSources || null;
var root = createContainer(container, tag, hydrate);
markContainerAsRoot(root.current, container);
var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
if (mutableSources) {
for (var i = 0; i < mutableSources.length; i++) {
var mutableSource = mutableSources[i];
registerMutableSourceForHydration(root, mutableSource);
}
}
return root;
}