本内容是基于vue2的源码分析,vue3与vue2原理一致。以下是我自己看源码的理解与笔记,若有误,请指出,谢谢。
创建组件时,以下代码执行顺序依次是:create → render → mounted
var KeepAlive = {
name: "keep-alive",
/**
* abstract属性为true,则表示该组件是一个抽象组件,
* 它自身不会渲染一个DOM元素,也不会出现在父组件链中,
* 执行逻辑在下面的initLifecycle函数中
*/
abstract: true,
// props中的属性就是我们使用keepAlive时传的
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
},
created: function () {
// 在该keepAlive组件上添加一个空的cache属性和空的keys属性,用来存后面的缓存组件
this.cache = Object.create(null);
this.keys = [];
},
render: function () {
var slot = this.$slots.default; // 获取keepAlive包裹的内容(slot)
var vnode = getFirstComponentChild(slot); // 获取slot中的第一个,所以keepAlive里包裹多个组件无效,只取第一个
var componentOptions = vnode && vnode.componentOptions; // 获取被包裹组件的componentOptions,若这个属性没有值,则该组件没有内容
if (componentOptions) {
// 如果不是空组件,就执行下面的内容
// check pattern
var name_2 = _getComponentName(componentOptions); // 获取被包裹组件(vnode)的name或tag
/**
* 如果keepAlive组件的exclude中 包含 vnode的name或tag,
* 或者keepAlive组件的include中 不包含 vnode的name或tag,
* 则直接return vnode,不执行后面的代码了
*/
var _a = this,
include = _a.include,
exclude = _a.exclude;
if (
// not included
(include && (!name_2 || !matches(include, name_2))) ||
// excluded
(exclude && name_2 && matches(exclude, name_2))
) {
return vnode;
}
/**
* 如果keepAlive组件的exclude中 不包含 vnode的name或tag,
* 或者keepAlive组件的include中 包含 vnode的name或tag,
* 则继续执行下面的代码
*/
var _b = this,
cache = _b.cache,
keys = _b.keys;
var key =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? "::".concat(componentOptions.tag) : "")
: vnode.key; // 按一定规则生成一个key作为被包裹组件vnode的标识
if (cache[key]) {
/**
* cache就是created中定义的,如果vnode的key之前缓存过,则执行下面的;
* vnode第一次进来的时候cache[key]一定不为真,会走else里的逻辑
*/
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove$2(keys, key);
keys.push(key);
} else {
/**
* vnode第一次进来时会进入此处的逻辑
*/
// delay setting the cache until update
this.vnodeToCache = vnode;
this.keyToCache = key;
}
// @ts-expect-error can vnode.data can be undefined
vnode.data.keepAlive = true; // 需要被缓存的组件的vnode打上一个标记
}
return vnode || (slot && slot[0]); // 如果vnode是空组件,或者vnode不是空组件且是需要缓存的,则return vnode或者keepAlive中的第一个slot
},
mounted: function () {
var _this = this;
this.cacheVNode(); // 此方法具体逻辑见下面methods中
// 监听黑白名单(exclude、include)中的变动,实时更新this.cache对象
this.$watch("include", function (val) {
pruneCache(_this, function (name) {
return matches(val, name);
});
});
this.$watch("exclude", function (val) {
pruneCache(_this, function (name) {
return !matches(val, name);
});
});
},
updated: function () {
this.cacheVNode();
},
destroyed: function () {
// keepAlive组件的destroyed生命周期执行时,会遍历缓存里的每个组件,删除缓存并销毁它们
for (var key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
methods: {
cacheVNode: function () {
var _a = this,
cache = _a.cache,
keys = _a.keys,
// 从上面的render()中可以知道,this.vnodeToCache只有当被包裹组件(vnode)是第一次加载时,才会存在
vnodeToCache = _a.vnodeToCache,
keyToCache = _a.keyToCache;
if (vnodeToCache) {
var tag = vnodeToCache.tag,
componentInstance = vnodeToCache.componentInstance,
componentOptions = vnodeToCache.componentOptions;
// 第一次加载vnode时,将它存到keepAlive的cache属性中
cache[keyToCache] = {
name: _getComponentName(componentOptions),
tag: tag,
componentInstance: componentInstance,
};
keys.push(keyToCache); // 把生成的vnode的key添加到keepAlive的keys中
// 如果缓存超过设置的最的的数量了,则删除最先存进来的一个
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode);
}
this.vnodeToCache = null; // 清空vnodeToCache
}
},
},
};
// 删除cache对象数据函数
function pruneCache(keepAliveInstance, filter) {
var cache = keepAliveInstance.cache,
keys = keepAliveInstance.keys,
_vnode = keepAliveInstance._vnode;
for (var key in cache) {
var entry = cache[key];
if (entry) {
var name_1 = entry.name;
if (name_1 && !filter(name_1)) {
pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
}
function pruneCacheEntry(cache, key, keys, current) {
var entry = cache[key];
if (entry && (!current || entry.tag !== current.tag)) {
// @ts-expect-error can be undefined
entry.componentInstance.$destroy(); //销毁组件
}
cache[key] = null;
remove$2(keys, key);
}
// vue组件的初始化生命周期函数
function initLifecycle(vm) {
var options = vm.$options;
// 定位第一个非abstract父组件实例
var parent = options.parent;
if (parent && !options.abstract) {
// 如果不是抽象组件
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
// 如果是抽象组件
parent.$children.push(vm);
}
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
vm.$children = [];
vm.$refs = {};
vm._provided = parent ? parent._provided : Object.create(null);
vm._watcher = null;
vm._inactive = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
Vue的渲染是从render阶段开始的,但keep-alive的渲染是在patch阶段,这是构建组件树(虚拟DOM树),并将VNode转换成真正DOM节点的过程。
在patch阶段,会执行createComponent函数:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
// isDef函数用于判断i是否存在,当存在时返回true
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; // 是否缓存过
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */); // 如果没有被缓存过,则代码执行到这里就结束
}
/**
* 调用init钩子后,如果vnode是一个子组件,
* 它应该创建一个子实例并挂载它,
* 子组件还设置了占位符vnode的elm.
* 在这种情况下,我们可以返回元素并完成。
*/
if (isDef(vnode.componentInstance)) {
/**
* keepAlive组件的render函数中可以看到,vnode.componentInstance是在被包裹组件
* 在非首次加载时赋值的,所以当非首次加载时,就直接往父元素中插入缓存中的dom
*/
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
}
}
// patch期间在组件VNode上调用的内联挂钩
var componentVNodeHooks = {
// 初始化组件钩子函数
init: function (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode); // 缓存过的组件不再走$mount,那么mounted之前的所有钩子函数(beforeCreate、created、mounted)都不再执行。
} else {
var child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
child.$mount(hydrating ? vnode.elm : undefined, hydrating); // 没有被缓存过的组件会调用$mount函数
}
},
prepatch: function (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = (vnode.componentInstance = oldVnode.componentInstance);
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
},
insert: function (vnode) {
var context = vnode.context,
componentInstance = vnode.componentInstance;
if (!componentInstance._isMounted) {
componentInstance._isMounted = true;
callHook$1(componentInstance, "mounted");
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
/**
* 在更新过程中,保持活动的组件的子组件可能会发生变化,因此直接在这里遍历树可能会在不正确的子组件上调用激活的钩子。
* 相反,我们将它们推入队列,在整个patch过程结束后进行处理
*/
queueActivatedComponent(componentInstance);
} else {
// activateChildComponent函数递归地去执行所有子组件的activated钩子函数
activateChildComponent(componentInstance, true /* direct */);
}
}
},
destroy: function (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
// 没有被缓存的组件则销毁
componentInstance.$destroy();
} else {
deactivateChildComponent(componentInstance, true /* direct */); // 当是缓存的组件则没有销毁,而是调用deactivateChildComponent()
}
}
},
};
参考博客:https://blog.csdn.net/weixin_57334089/article/details/121218541