keep-alive
keep-alive
是 Vue 的一个内置抽象组件,通常用于缓存动态组件或路由组件。
被 keep-alive
包裹的组件在切换时不会被销毁,而是会被缓存下来,下一次切换回这个组件时,会直接复用之前的实例,保持其状态。
keep-alive的几个配置属性和钩子:
1.include
和 exclude
: 用于控制哪些组件需要缓存,支持字符串、正则表达式或数组。
2.max
: 用于指定缓存的组件数量,当超出这个数量时,最久未使用的组件实例将被销毁。
3.activated
:当组件被激活时触发(即从缓存中恢复时)。
4.deactivated
:当组件被停用时触发(即被缓存时)。
keep-alive的核心原理:
keep-alive
是通过缓存组件实例来避免组件重复创建和销毁,达到性能优化的目的。它通过将已缓存的组件存储在内存中,当组件被重新激活时,直接复用之前缓存的实例,而不是重新创建。
核心原理步骤:
1.缓存实例:当组件被第一次加载时,keep-alive
会将组件的实例缓存起来。
2.组件复用:当你切换到一个已经被缓存的组件时,keep-alive
会从缓存中提取该组件的实例,而不是重新创建。
3.生命周期管理:为了处理组件的激活和停用,keep-alive
引入了 activated
和 deactivated
钩子,在组件进入或离开缓存时触发。
简单实现:
const KeepAlive = { name: 'keep-alive', // 定义组件的名字为 keep-alive abstract: true, // 将组件标记为抽象组件。抽象组件不会在父组件链中出现。 props: { include: [String, RegExp, Array], // include:指定哪些组件需要被缓存,可以是字符串、正则表达式或数组。 exclude: [String, RegExp, Array], // 指定哪些组件不需要被缓存,可以是字符串、正则表达式或数组 max: [String, Number] // 缓存组件的最大数量 }, created() { this.cache = Object.create(null); // 用于存储缓存的组件实例。 this.keys = []; // 用于存储缓存组件的键列表,按顺序保存。 }, destroyed() { for (const key in this.cache) { pruneCacheEntry(this.cache, key, this._vnode); // 遍历 this.cache 中的所有键,并调用 pruneCacheEntry 函数清理缓存 } }, mounted() { this.$watch('include', val => { pruneCache(this, name => matches(val, name)); // 监听 include 属性的变化,并调用 pruneCache 函数清理不匹配的缓存。 }); this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)); // 监听 exclude 属性的变化,并调用 pruneCache 函数清理匹配的缓存。 }); }, render() { // 渲染函数,定义组件的渲染逻辑。 const slot = this.$slots.default; // 获取默认插槽内容。 const vnode = getFirstComponentChild(slot); // 获取插槽内容中的第一个子组件虚拟节点。 const componentOptions = vnode && vnode.componentOptions; // 获取子组件的选项。 if (componentOptions) { // 子组件的选项 存在时,继续处理。 const name = getComponentName(componentOptions); const { include, exclude } = this; /** * 检查 include 和 exclude 属性。 * 如果 include 存在且组件名称不匹配,则直接返回 vnode。 * 如果 exclude 存在且组件名称匹配,则直接返回 vnode。 */ if ( (include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name)) ) { return vnode; } /** * 生成缓存键 key。 * 如果 vnode.key 为空,则使用组件构造函数的 cid 和标签名生成键。 * 否则,使用 vnode.key 作为键。 */ const key = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key; /** * 检查缓存中是否存在该键。 * 如果存在,从缓存中取出组件实例,并将键移到 this.keys 的末尾。 * 如果不存在,将组件虚拟节点存入缓存,并将键添加到 this.keys。 * 如果缓存数量超过 max,则清理最早的缓存。 */ if (this.cache[key]) { vnode.componentInstance = this.cache[key].componentInstance; remove(this.keys, key); this.keys.push(key); } else { this.cache[key] = vnode; this.keys.push(key); if (this.max && this.keys.length > parseInt(this.max)) { pruneCacheEntry(this.cache, this.keys[0], this._vnode); } } vnode.data.keepAlive = true; // 标记虚拟节点为 keepAlive } return vnode || (slot && slot[0]); // 返回虚拟节点或插槽中的第一个子节点 } }; /** * 清理缓存条目。 * 获取缓存条目 entry。 * 如果存在且不等于当前虚拟节点,销毁组件实例。 * 将缓存条目置为空。 */ function pruneCacheEntry(cache, key, current) { const entry = cache[key]; if (entry && (!current || entry.tag !== current.tag)) { entry.componentInstance.$destroy(); } cache[key] = null; } /** * 根据过滤条件清理缓存。 * 获取 cache、keys 和 _vnode。 * 遍历缓存,获取组件名称。 * 如果名称存在且不符合过滤条件,清理缓存条目。 */ function pruneCache(keepAliveInstance, filter) { const { cache, keys, _vnode } = keepAliveInstance; for (const key in cache) { const entry = cache[key]; if (entry) { const name = getComponentName(entry.componentOptions); if (name && !filter(name)) { pruneCacheEntry(cache, key, _vnode); } } } } /** * 检查名称是否匹配模式。 * 如果模式是数组,检查名称是否在数组中。 * 如果模式是字符串,拆分字符串并检查名称是否在其中。 * 如果模式是正则表达式,测试名称是否匹配。 * 否则返回 false。 */ function matches(pattern, name) { if (Array.isArray(pattern)) { return pattern.indexOf(name) > -1; } else if (typeof pattern === 'string') { return pattern.split(',').indexOf(name) > -1; } else if (pattern.test) { return pattern.test(name); } return false; } // 获取组件构造函数的 name 或标签名 function getComponentName(opts) { return opts && (opts.Ctor.options.name || opts.tag); } // 获取第一个子组件 过滤并返回第一个有 componentOptions 的子节点。 function getFirstComponentChild(children) { return children && children.filter(c => c && c.componentOptions)[0]; }
ps: Vue 3 的 keep-alive
原理还是缓存组件实例