著有《React 源码》《React 用到的一些算法》《javascript地月星》等多个专栏。欢迎关注。
文章不好写,要是有帮助别忘了点赞,收藏,评论 ~你的鼓励是我继续挖干货的动力🔥。
另外,本文为原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解~
前言
上一篇是React Fiber树“头部”的构建,还梳理了“头部”的FiberRootNode HostRootFiber HostRoot。这一篇继续梳理“头部”的容器节点,因为一些东西容易搞混。
- HostRootFiber和div#root是不是对应的?
- div#root是不是App的根节点?
- HostPortal和div#modal-container是不是对应的?
都不是。div#root、div#modal-container是HostRootFiber、HostPortal的容器节点(containerInfo)。 还要补充,div#modal-container有自己另外对应的HostComponent类型的Fiber。div#root没有对应的HostComponent Fiber。
stateNode和containerInfo
每个Fiber节点都有stateNode属性,对于HostComponent(对应着真实DOM)类型的Fiber节点,它们的stateNode保存着真实DOM的索引。
containerInfo是容器的索引,只有HostRoot、HostPortal类型的Fiber节点有容器。
区别是什么?
HostComponent是从DOM创建过来的,进行了属性、事件上的关联。
HostRoot、HostPortal不是从容器创建过来的,没有进行这样的关联。
例如,对于根节点div#root,它刚好落在App的“DOM父节点”,所以容易误解为App的Fiber父节点对应的DOM。
- HostRootFiber,tag=HostRoot, 是App的父节点
HostRootFiber.child = App;
App.return = HostRootFiber。
- 表面上看都是App的“父节点”,但是HostRoot和div#root不是对应的(不是从div#root创建过来):
graph LR
A["❌ HostRootFiber, tag=HostRoot"]
B["div#root"]
A --stateNode或containerInfo--> B
confinerInfo不是这样用的,这里至少要改成stateNode。
但是同时,div#root不是HostRootFiber对应的DOM实例,所以用stateNode也不对。
都不对。
容器节点 containerInfo
stateNode不全指真实的DOM节点,那仅仅是在HostComponent,这些节点是从DOM创建过来的,当然有DOM,但是函数组件呢,类组件呢,HostRoot呢都没有DOM。
在HostRoot和HostPortal中,都需要一个容器。 stateNode发挥的另一个作用,通过它找到一个对象,在这个对象上找到容器。
- HostRoot的容器节点div#root
HostRoot.stateNode = FiberRootNode
FiberRootNode.containerInfo = div#root//容器
div#root.$reactFiber... 没有这个属性,div#root没有自己另外的HostComponent Fiber
- HostPortal的容器节点div#modal-container
HostPortal.stateNode = {...containerInfo...}//一个普通的对象,描述HostPortal
{...containerInfo...}.containerInfo = div#modal-container//容器
div#modal-container.__reactFiber$... = Fiber,tag=HostComponent//容器的Fiber,不是HostPortl了,是另一个独立的Fiber,有自己另外的HostComponent Fiber
Fiber,tag=HostComponent.stateNode = div#modal-container
每个DOM上也有一个__reactFiber$...属性关联到它的Fiber(div#root没有)。
可以想象出大概是这么一个关系:
从左到右是HostRoot Fiber子树、包含HostPortal的Fiber树、包含HostPortal的DOM树。
蓝色是Fiber实例,黄色是普通对象,红色是DOM实例。
ReactDOM.createPortal(<div>具体内容</div>,document.getElementById('可以指定任意位置子树作为容器'))。 图里面的Fiber子树、DOM子树看起来像失联了,其实是有联系的,不是游离的,因为Fiber子树、DOM子树不知道会在哪个节点下面,可能在body下,可能在div下,所以没有画上去。
关联阅读:
源码
function createFiberFromPortal(portal, mode, lanes) {
var pendingProps = portal.children !== null ? portal.children : [];
var fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
fiber.lanes = lanes;
fiber.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null,
// Used by persistent updates
implementation: portal.implementation
};
return fiber;
} // Used for stashing WIP properties to replay failed work in DEV.
// 从mountIndeterminateComponent renderWithHooks创建
// portal: {
// $$typeof: Symbol(react.portal)
// children: {$$typeof: Symbol(react.element), type: 'div', key: null, ref: null, props: {…}, …}
// containerInfo: div#modal-container
// implementation: null
// key: null
// [[Prototype]]: Object
// }
function updatePortal(returnFiber, current, portal, lanes) {
if (current === null || current.tag !== HostPortal || current.stateNode.containerInfo !== portal.containerInfo || current.stateNode.implementation !== portal.implementation) {
// Insert
var created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
// Update
var existing = useFiber(current, portal.children || []);
existing.return = returnFiber;
return existing;
}
}
结语
可以简单理解,stateNode发挥着2个作用,1.在HostComponent中指向DOM实例。2.在HostRoot,HostPortal中指向一个 包含containerInfo属性的对象,containerInfo指向容器。