十、异步组件
异步组件实现
直接在 vue 文件内使用 defineAsyncComponent
方法定义异步组件,并直接 import 导入
defineAsyncComponent 最简实现(异步实现实际上就是使用 promise 的 then 方法):
// 接收一异步加载器
function defineAsyncComponent(loader) {
// 存储需要异步加载的组件
let InnerComp = null;
// 返回封装好的组件
return {
name: "AsyncComponentWrapper",
setup() {
// 异步组件是否加载成功
const loaded = ref(false);
// 执行加载器函数,返回一个 Promise 实例
// 加载成功后,将加载成功的组件赋值给 InnerComp,并将 loaded 标记为 true,代表加载成功
loader().then((c) => {
InnerComp = c;
loaded.value = true;
});
return () => {
// 如果异步组件加载成功,则渲染该组件,否则渲染一个占位内容
return loaded.value
? { type: InnerComp }
: { type: Text, children: "" };
};
},
};
}
超时问题
根据用户网络环境,应设置超时时长,当某组件加载时间超过该时长后就报错;
原理即设置一个定时器,在组件开始加载时计时,超过设定的 timeout 就报错,代码添加在 defineAsyncComponent 方法里
函数式组件
增补
十一、内建组件
keepalive
keepalive 是为了保证一个组件可以复用而无需每次都卸载重建浪费资源
被 keepalive 包裹的组件,会有以下两种状态:
- 卸载(deactivated)状态:组件并不是真的卸载,而是放到一个隐藏容器中
- 挂载(activated)状态:从隐藏容器中再拿出组件的过程
keepalive 实现代码
const KeepAlive = {
// KeepAlive 组件独有的属性,用作标识
__isKeepAlive: true,
setup(props, { slots }) {
// 创建一个缓存对象
// key: vnode.type
// value: vnode
const cache = new Map()
// 当前 KeepAlive 组件的实例
const instance = currentInstance
// 对于 KeepAlive 组件来说,它的实例上存在特殊的 keepAliveCtx 对象,该对象由渲染器注入
// 该对象会暴露渲染器的一些内部方法,其中 move 函数用来将一段 DOM 移动到另一个容器中
const { move, createElement } = instance.keepAliveCtx
// 创建隐藏容器
const storageContainer = createElement('div')
// 卸载和挂载的方法
instance._deActivate = (vnode) => {
move(vnode, storageContainer)
}
instance._activate = (vnode, container, anchor) => {
move(vnode, container, anchor)
}
return () => {
// KeepAlive 的默认插槽就是要被 KeepAlive 的组件
let rawVNode = slots.default()
// 如果不是组件,直接渲染即可,因为非组件的虚拟节点无法被 KeepAlive
if (typeof rawVNode.type !== 'object') {
return rawVNode
// 在挂载时先获取缓存的组件 vnode
const cachedVNode = cache.get(rawVNode.type)
if (cachedVNode) {
// 如果有缓存的内容,则说明不应该执行挂载,而应该执行激活
// 继承组件实例
rawVNode.component = cachedVNode.component
// 在 vnode 上添加 keptAlive 属性,标记为 true,避免渲染器重新挂载它
rawVNode.keptAlive = true
} else {
// 如果没有缓存,则将其添加到缓存中,这样下次激活组件时就不会执行新的挂载动作了
cache.set(rawVNode.type, rawVNode)
// 在组件 vnode 上添加 shouldKeepAlive 属性,并标记为 true,避免渲染器真的将组件卸载
rawVNode.shouldKeepAlive = true
// 将 KeepAlive 组件的实例也添加到 vnode 上,以便在渲染器中访问
rawVNode.keepAliveInstance = instance
// 渲染组件 vnode
return rawVNode
}
}
}
包含与否
keepalive 可以根据用户传入的数据决定是否要缓存组件;
include 属性:表示需要缓存的组件名
exclude 属性:表示不需要缓存的组件名
俩属性一般使用正则进行匹配,下面是 keepalive 的模板:
const keepalive = {
_isKeepalive: true,
props: {
include: /regexp/,
exclude: /regexp/,
},
setup(props, { slots }) {
return () => {};
},
};
缓存
当缓存不存在时会设置新的缓存,此时需要存在一个缓存阈值来控制缓存数量不能无限的增长下去;
可参考 JAVA 的 GC 中新老生代的方法,如果超出缓存阈值,那就清理掉最先使用的缓存;
teleport 组件
teleport 组件的作用:跨 DOM 层级渲染,避免 z-index 影响
teleport 最简实现
teleport 的使用模板,还有很多问题没有解决,这里仅提供参考!
const teleport = {
_isTeleport: true,
process(n1, n2, container, anchor, internals) {
// 获取渲染器内部方法
const { patch } = internals;
// 若干旧vnode不存在,则挂载新的vnode
if (!n1) {
// 获取挂载点
const target =
typeof n2.props.to === "string"
? document.querySelector(n2.props.to)
: n2.props.to;
// 将新vnode的子节点挂载到指定挂载点
n2.children.forEach((c) => patch(null, c, target, anchor));
} else {
// 更新代码
}
},
};
transition
transition 俩要素:
DOM 被挂载,则添加动效到该 DOM
DOM 被卸载,等动效执行完毕再卸载
实现原理
假设有如下代码
<Transition>
<div>123</div>
</Transition>
transition 内部整体视为一个插槽 slot
故此为 transition 的基本实现源码
const Transition = {
name: "Transition",
setup(props, { slots }) {
return () => {
// 通过默认插槽获取过渡元素
const innerVnode = slots.default();
// 对应动效加载各个阶段的钩子函数
innerVnode.transition = {
beforeEnter(ele) {},
enter(ele) {},
leave(ele, performRemove) {},
};
return innerVnode;
};
},
};