源码位置:
runtime-core/src/components/Teleport
我们都知道渲染组件是由渲染器完成,Teleport
当然也是一个组件,只不过它比较特殊而已。
特殊就在于该组件的渲染逻辑不应该放在渲染器中
,因为如果没有使用到该组件,通过树摇
最终的构建包不会包含这部分代码。另外也是为了给渲染器 瘦身
哈!
所以需要在patch
函数中判断是否是Teleport组件,再将控制权交还
给Teleport组件,另外会再将一些渲染器的方法
也传递给Teleport
js
else if (typeof type === 'object' && type.__isTeleport) {
// 组件选项中如果存在 __isTeleport 标识,则它是 Teleport 组件,
// 调用 Teleport 组件选项中的 process 函数将控制权交接出去
type.process(n1, n2, container, anchor, {
patch,
patchChildren,
unmount,
move(vnode, container, anchor) {
insert(vnode.component ? vnode.component.subTree.el :
ode.el, container, anchor)
}
})
}
对于组件来说,例如keppAlive组件的子节点会被编译为插槽内容, 不过对于 Teleport 组件来说,直接将其子节点编译为一个数组
即可
js
//代表Teleport组件的VNode
{
type: Teleport,
// 以普通 children 的形式代表被 Teleport 的内容
children: [
{ type: 'h1', children: 'Title' },
{ type: 'p', children: 'content' }
]
}
如果是第一次挂载,需要根据Teleport组件上的to属性找到挂载点,接着将组件内的元素循环放在挂载点之下
js
process(n1, n2, container, anchor, internals) {
// 通过 internals 参数取得渲染器的内部方法
const { patch } = internals
// 如果旧 VNode n1 不存在,则是全新的挂载,否则执行更新
if (!n1) {
// 挂载
// 获取容器,即挂载点
const target = typeof n2.props.to === 'string'
? document.querySelector(n2.props.to)
: n2.props.to
// 将 n2.children 渲染到指定挂载点即可
n2.children.forEach(c => patch(null, c, target, anchor))
}
else {
//更新
}
}
对于更新,只需要调用patchChildren
比较子节点并替换。但是如果是Teleport组件的to属性改变了,代表要修改挂载点。因为子节点已经被替换,所以只需要移动到新的挂载点即可。
但是对于组件(vnode.component.subTree.el)
和普通标签(vnode.el)
实例存放的位置不一样,需要区分.所以渲染器传递的move函数封装了insert函数返回给Teleport使用,另外对于文本节点,片段也需要判断,这里只介绍这两种情况。
js
else {
// 更新
patchChildren(n1, n2, container);
// 如果新旧 to 参数的值不同,则需要对内容进行移动
if (n2.props.to !== n1.props.to) {
// 获取新的容器
const newTarget =
typeof n2.props.to === "string"
? document.querySelector(n2.props.to)
: n2.props.to;
// 移动到新的容器
n2.children.forEach((c) => move(c, newTarget));
}
}
js
move(vnode, container, anchor) {
insert(
vnode.component
? vnode.component.subTree.el // 移动一个组件
: vnode.el, // 移动普通元素
container,
anchor
);
}