【vue设计与实现】内部组件和模块 1 - KeepAlive组件的实现原理

组件的激活和失活
KeepAlive一词来自于HTTP协议,在HTTP协议里,KeepAlive又称HTTP持久链接(HTTP persistent connection),其作用是允许多个请求或响应共用一个TCP连接。如果没有KeepAlice,一个HTTP连接会在每次请求/响应结束后关闭,当下一次请求发生时,会建立一个新的HTTP连接,频繁地销毁,创建HTTP连接会带来额外的性能开销,KeepAlive就是为了解决这个问题的。

与HTTP中的KeepAlive类似,Vue.js内建的KeepAlive组件可以避免一个组件被频繁地销毁/重建。
如下面代码:

<template>
	<!-- 使用KeepAlive 组件包裹 -->
	<KeepAlive>
		<Tab v-if="currentTab === 1">...</Tab>
		<Tab v-if="currentTab === 2">...</Tab>
		<Tab v-if="currentTab === 3">...</Tab>
	</KeepAlive>
</template>

这样,无论用户怎样切换<Tab>组件,都不会发生频繁的创建和销毁,因而会极大地优化对用户操作的相应。
其实KeepAlive的本质是缓存管理,再加上特殊的挂载/卸载逻辑

首先,KeepAlive组件的实现需要渲染器层面的支持。这是因为被KeepAlive的组件在卸载时,不是真正的卸载,正确的做法是,将被KeepAlive的组件从原容器搬运到另外一个隐藏的容器中,实现“假卸载”
当被搬运到隐藏容器中的组件需要再次被“挂载”时,也不能执行真正的挂载逻辑,而是应该把该组件从隐藏容器中再搬运到原容器。这个过程对应到组件的生命周期,其实就是activated和deactivated

一个最基本的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')

		// KeepAlive 组件的实例上会被添加两个内部函数,分别是_deActive和_activate
		// 这两个函数会在渲染器中被调用
		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组件与渲染器结合非常深,
首先,KeepAlive组件本身不会渲染额外的内容,其渲染函数最终值返回需要被KeepAlive的组件,也叫“内部组件”。KeepAlive对“内部组件”的操作主要是在vnode对象上添加一些标记属性,以便渲染器能够据此执行特定的逻辑。这些标记属性包括如下几个:

  1. shouldKeepAlive,该属性会被添加到“内部组件”的vnode对象上,这样当渲染器卸载“内部组件”时,到知道要被KeepAlive时,会调用_deActivate函数完成搬运工作,如下面代码所示:

    // 卸载操作
    function unmount(vnode){
    	if(vnode.type === Fragment){
    		vnode.children.forEach(c=>unmount(c))
    		return
    	}else if(typeof vnode.type === 'object'){
    		if(vnode.shouldKeepAlive){
    			// 使用_deActivate函数
    			vnode.keepAliveInstance._deActivate(vnode)
    		}else{
    			unmount(vnode.component.subTree)
    		}
    		return
    	}
    	const parent = vnode.el.parentNode
    	if(parent){
    		parent.removeChild(vnode.el)
    	}
    }
    
  2. keepAliveInstance: 是“内部组件”的vnode对象持有的KeepAlive组件实例

  3. keptAlive:标记“内部组件”是否已经被缓存,这样当“内部组件”需要重新渲染时,渲染器并不会重新挂载它,而会将其激活,如下面patch函数的代码所示:

    function patch(n1,n2,container,anchor){
    	if(n1 && n1.type !== n2.type{
    		unmount(n1)
    		n1 = null
    	})
    
    	const {type} = n2
    	if(typeof type === 'string'){
    		// 省略部分代码
    	}else if(typeof type === Text){
    		// 省略部分代码
    	}else if(typeof type === Fragment){
    		// 省略部分代码
    	}else if(typeof type === 'object' || typeof type === 'function'){
    		// component
    		if(!n1){
    			// 如果该组件已经被KeepAlive,则不会重新挂载它,而是会调用_activate来激活它
    			if(n2.keptAlive){
    				n2.keepAliveInstance._activate(n2,container,anchor)
    
    			}else{
    				mountComponent(n2,container,anchor)
    			}
    		}else{
    			patchComponent(n1,n2,anchor)
    		}
    	}
    }
    

再来看用于激活组件和失活组件的两个函数:

const {move, createElement} = instance.keepAliveCtx
instnce._deActivate = (vnode) => {
	move(vnode,storageContainer)
}

instance._activate = (vnode,container,anchor) => {
	move(vnode,container,anchor)

}

可以看到,失活的本质就是将组件所渲染的内容移动到隐藏容器中,而激活的本质是将组件所渲染的内容从隐藏容器中搬运会原来的容器
另外,上面这段代码中涉及的move函数是由渲染器注入的,如下面mountComponent函数的代码所示:

function mountComponent(vnode,container,anchor){
	// 省略部分代码
	const instance = {
		state, 
		props: shallowReactive(props),
		isMounted: false,
		subTree: null,
		slots,
		mounted: [],
		// 只有KeepAlive组件的实例下会有keepAliveCtx属性
		keepAliveCtx: null
	}

	// 检查当前要挂载的组件是否是KeepAlive组件
	const isKeepAlive = vnode.type._isKeepAlive
	if(isKeepAlive){
		// 在KeepAlive组件实例上添加keepAliveCtx对象
		instance.keepAliveCtx = {
			// move函数用来移动一段vnode
			move(vnode,container,anchor){
				// 本质上是将组件渲染的内容移动到指定容器中,即隐藏容器中
				insert(vnode.component.subTree.el, container, anchor)
			},
			createElement
		}
	}

	// 省略部分代码
}

include和exclude

默认情况下,KeepAlive组件会对所有“内部组件”进行缓存。但有时候用户期望只缓存特定组件。为了使用用户能够自定义缓存规则,需要让KeepAlive组件支持两个props,分别是include和exclude,其中,include用来显式地配置应该被缓存组件,而exclude用来显式地配置不应该被缓存组件
KeepAlive组件的props定义如下:

const KeepAlive = {
	_isKeepAlive: true,
	// 定义include和exclude
	props:{
		include:RegExp,
		exclude:RegExp
	},
	setup(props, {slots}){
		// 省略部分代码
	}
}

为了简化问题,只允许include和exclude设置正则类型的值。在KeepAlive组件被挂载时,会根据“内部组件”的名称(即name选项)进行匹配,如下面的代码所示:

const cache = new Map()
const KeepAlive = {
	_isKeepAlive: true,
	props:{
		include:RegExp,
		exclude:RegExp
	},
	setup(props, {slots}){
		// 省略部分代码

		return () => {
			let rawVNode = slots.default()
			if(typeof rawVNode.type !== 'object'){
				return rawVNode
			}
			// 获取“内部组件”的name
			const name = rawVNode.type.name
			// 对name进行匹配
			if(name && 
				{
					// 如果name无法被include匹配
					(props.include && !props.include.test(name)) || 
					// 或者被exclude匹配
					(props.exclude && props.exclude.test(name))
				}
			){
				// 则直接渲染“内部组件”,不对其进行后续的缓存操作
				return rawVNode
			}

			// 省略部分代码
		}
	}
}

注意根据用户指定的include和exclude正则,对“内部组件”的名称进行匹配,并根据匹配结果判断是否要对“内部组件”进行缓存。
在这个基础上,可以任意扩充匹配能力,例如将include和exclude设计成多种类型值,允许用户指定字符串或函数,从而提供更灵活的匹配机制。另外,在做匹配时,也可以让用户自行指定匹配要素。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,关于用Vue实现keep-alive组件的问题,我可以给您回答。 在Vue中,可以使用内置的keep-alive组件实现组件缓存和复用的功能。使用keep-alive包裹的组件会被缓存起来,当组件被切换时,可以直接从缓存中获取组件实例,避免了重复创建和销毁组件,提高了应用的性能。 以下是一个使用keep-alive组件的例子: ``` <template> <div> <keep-alive> <component :is="currentComponent"></component> </keep-alive> </div> </template> <script> export default { data() { return { currentComponent: 'ComponentA' } } } </script> ``` 在上面的例子中,我们将一个动态组件包裹在keep-alive中,并通过绑定is属性来指定当前显示的组件。当切换组件时,Vue会自动从缓存中获取或销毁组件实现组件的缓存和复用。 ### 回答2: Vue中的keep-alive组件是用来缓存动态组件的,可以提高性能和用户体验。下面是一个使用Vue实现keep-alive组件的步骤: 1. 首先,需要在Vue的根组件中引入keep-alive组件,并在模板中使用<keep-alive></keep-alive>标签包裹需要缓存的动态组件。 2. 在需要缓存的组件中,可以通过设置<keep-alive>标签的include属性,指定需要缓存的组件。例如,<keep-alive :include="['组件A', '组件B']"></keep-alive>,表示只有组件A和组件B会被缓存,其他组件每次都会被重新创建。 3. 可以通过设置<keep-alive>标签的exclude属性,排除不需要被缓存的组件。例如,<keep-alive :exclude="['组件C', '组件D']"></keep-alive>,表示除了组件C和组件D,其他组件都会被缓存。 4. 可以通过设置<keep-alive>标签的max属性,控制最大缓存的组件实例数量。例如,<keep-alive :max="5"></keep-alive>,表示最多只缓存5个组件实例,超过数量的组件实例会被销毁。 5. 在需要缓存的动态组件中,可以通过定义activated和deactivated生命周期钩子函数,来处理组件在被缓存和激活时的相关逻辑。例如,在activated生命周期钩子函数中可以执行组件被激活时的一些操作。 通过以上步骤,我们可以使用Vue的keep-alive组件来缓存需要动态加载的组件,提高性能和用户体验。 ### 回答3: 在Vue中,可以使用<keep-alive>组件实现组件的缓存和复用。 首先,在需要进行缓存的组件外部包裹<keep-alive>标签。例如: ``` <template> <div> <keep-alive> <router-view></router-view> </keep-alive> </div> </template> ``` 然后,在需要被缓存的组件中使用<keep-alive>的include属性来指定需要缓存的组件名称。例如: ``` <template> <div> <button @click="count++">{{ count }}</button> </div> </template> <script> export default { name: 'CachedComponent', data() { return { count: 0 } }, beforeRouteLeave(to, from, next) { if (to.name !== 'CachedComponent') { this.$destroy(); } next(); } } </script> ``` 在以上代码中,定义了一个带有计数器的组件,并在beforeRouteLeave生命周期钩子中实现了销毁组件的逻辑。这样,当从CachedComponent组件切换到其他组件时,CachedComponent组件会被销毁。 最后,在路由配置中,将需要使用缓存功能的组件的name属性设置为需要缓存的组件名称。例如: ``` const router = new VueRouter({ routes: [ { path: '/', name: 'CachedComponent', component: CachedComponent }, { path: '/other', name: 'OtherComponent', component: OtherComponent } ] }) ``` 通过以上步骤,就可以在Vue实现组件的缓存和复用了。使用<keep-alive>组件包裹需要缓存的组件,设置include属性指定需要缓存的组件名称,将需要使用缓存的组件的name属性设置为需要缓存的组件名称。这样,在切换到其他组件并再次回到需要缓存的组件时,Vue会直接复用之前的组件实例,而不会重新创建。这样可以提高组件的渲染效率和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值