跳至主要內容

17. 实现 Teleport


17. 实现 Teleport

1.Teleport组件介绍

render(h(Teleport, { to: '#root' }, [123, 456]), app);

Vue3 新增组件,该组件可以将制定内容渲染到制定容器中。默认内容都是渲染到元素app内,我们可以将其渲染到任意节点 (传送门)

const shapeFlag = isString(type)
	? ShapeFlags.ELEMENT
	: isTeleport(type) // 如果是穿梭框
	? ShapeFlags.TELEPORT
	: isObject(type)
	? ShapeFlags.STATEFUL_COMPONENT
	: isFunction(type)
	? ShapeFlags.FUNCTIONAL_COMPONENT
	: 0; // 函数式组件

创建虚拟节点的时候标识组件类型。

2.Teleport 挂载

if (shapeFlag & ShapeFlags.TELEPORT) {
	type.process(n1, n2, container, anchor, {
		mountChildren, // 挂载孩子
		patchChildren, // 更新孩子
		move(vnode, container, anchor) {
			// 移动元素
			hostInsert(
				vnode.component ? vnode.component.subTree.el : vnode.el,
				container,
				anchor
			);
		}
	});
}
export const TeleportImpl = {
	__isTeleport: true,
	remove(vnode, unmount) {
		const { shapeFlag, children } = vnode;
		if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
			for (let i = 0; i < children.length; i++) {
				const child = children[i];
				unmount(child);
			}
		}
	},
	process(n1, n2, container, anchor, internals) {
		let { mountChildren, patchChildren, move } = internals;
		if (!n1) {
			// 创建一个目标
			const target = (n2.target = document.querySelector(n2.props.to));
			if (target) {
				mountChildren(n2.children, target, anchor);
			}
		} else {
			patchChildren(n1, n2, container); // 比对儿子
			if (n2.props.to !== n1.props.to) {
				// 更新并且移动位置
				// 获取下一个元素
				const nextTarget = document.querySelector(n2.props.to);
				n2.children.forEach((child) => move(child, nextTarget, anchor));
			}
		}
	}
};
export const isTeleport = (type) => type.__isTeleport;

3.Teleport 卸载

const unmount = (vnode) => {
	const { shapeFlag } = vnode;
	if (vnode.type === Fragment) {
		unmountChildren(vnode.children);
	} else if (shapeFlag & ShapeFlags.COMPONENT) {
		unmount(vnode.component.subTree);
	} else if (shapeFlag & ShapeFlags.TELEPORT) {
		vnode.type.remove(vnode, unmount);
	} else {
		hostRemove(vnode.el);
	}
};