PortalComponent
1 )概述
- React Portal之所以叫Portal,因为做的就是和“传送门”一样的事情
- render到一个组件里面去,实际改变的是网页上另一处的DOM结构
- 主要关注 portal的创建, 调和, 更新过程
2 )源码
定位到 packages/react-dom/src/client/ReactDOM.js#L576
function createPortal(
children: ReactNodeList,
container: DOMContainer,
key: ?string = null,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
// TODO: pass ReactDOM portal implementation as third argument
return ReactPortal.createPortal(children, container, null, key);
}
-
这里调用的是
ReactPortal.createPortal
, 进入// packages/shared/ReactPortal.js#L14 export function createPortal( children: ReactNodeList, containerInfo: any, // TODO: figure out the API for cross-renderer implementation. implementation: any, key: ?string = null, ): ReactPortal { return { // This tag allow us to uniquely identify this as a React Portal $$typeof: REACT_PORTAL_TYPE, key: key == null ? null : '' + key, children, containerInfo, // dom 挂载节点 implementation, }; }
- 这里返回一个对象,类似于 ReactElement
- 区别在于
$$typeof
和containerInfo
需要的挂载点
-
对于
REACT_PORTAL_TYPE
类型的组件在 reconcile 时, 看下具体操作, 找到reconcileSinglePortal
文章来源:https://www.toymoban.com/news/detail-810808.html// packages/react-reconciler/src/ReactChildFiber.js#L1171 function reconcileSinglePortal( returnFiber: Fiber, currentFirstChild: Fiber | null, portal: ReactPortal, expirationTime: ExpirationTime, ): Fiber { const key = portal.key; // 当前的 key let child = currentFirstChild; // 如果 存在 child, 对比 child.key === key while (child !== null) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { // 关注这里,有 containerInfo 的对比,portal 需要关心渲染到的节点是否有变化 // 如果节点有变化,那么这个 portal 的渲染过程也会有变化 // 都符合,说明老节点都可以复用 if ( child.tag === HostPortal && child.stateNode.containerInfo === portal.containerInfo && child.stateNode.implementation === portal.implementation ) { deleteRemainingChildren(returnFiber, child.sibling); // 删除其他节点 // 通过 useFiber 复用这个节点 const existing = useFiber( child, portal.children || [], expirationTime, ); existing.return = returnFiber; return existing; } else { // key相同,但是不符合上述if, 没法复用,删除干净 deleteRemainingChildren(returnFiber, child); break; } } else { // key 不同,删除当前节点 deleteChild(returnFiber, child); } child = child.sibling; } // 创建一个 portal 的 fiber 对象 const created = createFiberFromPortal( portal, returnFiber.mode, expirationTime, ); created.return = returnFiber; return created; }
-
对于更新一个portal节点,进入
updatePortalComponent
文章来源地址https://www.toymoban.com/news/detail-810808.html// packages/react-reconciler/src/ReactFiberBeginWork.js#L1322 function updatePortalComponent( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { // 挂载点处理 pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo); const nextChildren = workInProgress.pendingProps; // 这里 pendingProps 是 children if (current === null) { // Portals are special because we don't append the children during mount // but at commit. Therefore we need to track insertions which the normal // flow doesn't do during mount. This doesn't happen at the root because // the root always starts with a "current" with a null child. // TODO: Consider unifying this with how the root works. workInProgress.child = reconcileChildFibers( workInProgress, null, nextChildren, renderExpirationTime, ); } else { reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); } return workInProgress.child; }
- 之前梳理过的 API 不再赘述
- 一些API的调用,在源码的注释中
到了这里,关于React16源码: React中的PortalComponent创建, 调和, 更新的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!