第14章、内建组件和模块
14.1 KeepAlive 组件的实现原理
KeepAlive 一词借鉴了 HTTP 协议。
KeepAlive 组件可以避免组件被频繁的销毁/重建。本质是缓存管理,再加上特殊的挂载卸载逻辑。
卸载时将组件放入另一个容器中,再次挂载时再拿出来。对应生命周期为 activated
和 deactivated
。
我们创建内置组件 KeepAlive 后,需要在卸载和挂载时对它进行特殊处理。
const KeepAlive = {
__isKeepAlive: true,
setup(props, { slots }) {
// 用于缓存组件实例
const cache = new Map()
// currentInstance 前几章为了生命周期存的全局变量
// 记录组件的实例
const instance = currentInstance
// 从实例的属性中获取一些渲染器的内部方法
const { move, createElement } = instance.keepAliveCtx
// 创建隐藏容器
const storageContainer = createElement('div')
instance._deActivate = (vnode) => {
move(vnode, storageContainer)
}
instance._activate = (vnode) => {
move(vnode, container, anchor)
}
return () => {
// 默认插槽
let rawVNode = slots.default()
// 不是组件直接渲染 因为非组件不能缓存
if (typeof rawVNode.type !== 'object') {
return rawVNode
}
const cachedVNode = cache.get(rawVNode.type)
if (cachedVNode) {
rawVNode.component = cachedVNode.component
// 如果是缓存组件 后面就不需要再挂载初始化了
rawVNode.keptAlive = true
} else {
cache.set(rawVNode.type, rawVNode)
}
rawVNode.shouldKeepAlive = true
rawVNode.keepAliveInstance = instance
return rawVNode
}
},
}
在 patch
的组件挂载逻辑部分新加判断:
if (!n1) {
if (n2.keptAlive) {
n2.keepAliveInstance._activate(n2, container, anchor)
}
// 挂载
mountComponent(n2, container, anchor)
}
卸载也一样
function unmount(vnode) {
if (vnode.type === Fragment) {
// ...
} else if (typeof vnode.type === 'object') {
if (vnode.shouldKeepAlive) {
// 如果是keepalive组件 卸载时调用组件的deactive方法
vnode.keepAliveInstance._deActivate(vnode)
} else {
unmount(vnode.component.subTree)
}
return
}
// ...
}
同时在组件实例新增 keepAliveCtx
,传入 move
和 createElement
让组件在 activated
和 deactivated
时使用。
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
slots,
// 在组件实例中添加 mounted 数组,用来存储通过 onMounted 函数注册的生命周期函数
mounted: [],
// keepAlive组件实例下保存
keepAliveCtx: null,
}
const isKeepAlive = vnode.type.__isKeepAlive
if (isKeepAlive) {
instance.keepAliveCtx = {
move(vnode, container, anchor) {
insert(vnode.component, subTree.el, container, anchor)
},
createElement,
}
}
KeepAlive 组件还支持 include
和 exclude
功能,实现原理就是在判断是否缓存时加一个正则匹配。
缓存管理,需要限定缓存数量,在 Vue 中采取最近一次访问优先策略。
以及考虑如何在 KeepAlive 中支持自定义策略。
14.2 Teleport 组件的实现原理
通常情况下,组件渲染成真实 DOM 的层级结构与虚拟 DOM 相同,但是如果我们想要渲染到其他位置就无法实现。比如我们想要渲染一个蒙层,需要挂载到 body 下面。
实现方式,给组件增加标识,patch
挂载时挂载到指定的位置即可。为了方便 TreeShaking 把渲染逻辑封装到组件内部,这样如果用户没有使用到 Teleport 组件就不会加载相关代码。
14.3 Transition 组件的实现原理
原理:
- 组件被挂载时,将动效附加到该 DOM 上
- 组件被卸载时,不要立即卸载 DOM 元素,等到 DOM 上动效执行完再卸载
在挂载时我们有 enter-from
和 enter-to
两种,我们需要在开始时添加 enter-from
类名,然后在下一帧再添加 enter-to
触发动画。由于浏览器限制,我们要通过嵌套的 requestAnimationFrame
执行。
等到过渡完成,通过 transitionend
事件判断,并删除类。卸载同理。