您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue中的KeepAlive組件怎么使用”,在日常操作中,相信很多人在Vue中的KeepAlive組件怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Vue中的KeepAlive組件怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
<KeepAlive>
是一個內置組件,它的功能是在多個組件間動態切換時緩存被移除的組件實例。
KeepAlive 一詞借鑒于 HTTP 協議,在 HTTP 協議里面 KeepAlive 又稱持久連接,作用是允許多個請求/響應共用同一個 HTTP 連接,解決了頻繁的銷毀和創建 HTTP 連接帶來的額外性能開銷。而同理 Vue 里的 KeepAlive 組件也是為了避免一個組件被頻繁的銷毀/重建,避免了性能上的開銷。
// App.vue
<Test :msg="curTab" v-if="curTab === 'Test'"></Test>
<HelloWorld :msg="curTab" v-if="curTab === 'HelloWorld'"></HelloWorld>
<div @click="toggle">toggle</div>
上述代碼可以看到,如果我們頻繁點擊 toggle 時會頻繁的渲染 Test/HelloWorld 組件,當用戶頻繁的點擊時 Test 組件需要頻繁的銷毀/渲染,這就造成很大的渲染性能損失。
所以為了解決這種性能開銷,你需要知道是時候使用 KeepAlive 組件。
<KeepAlive>
<component :is="curTab === 'Test' ? Test : HelloWorld" :msg="curTab"></component>
</KeepAlive>
<div @click="toggle">toggle</div>
可以看這個錄屏,在首次加載后再次頻繁的切換并沒有重新銷毀與掛載,而僅僅是將組件進行了失活(而不是銷毀),渲染時只需要重新激活就可以,而不需重新掛載,如果要渲染的組件很大,那就能有不錯的性能優化。
想要體驗的話可以去看看這個例子?官方demo,其中數據會被緩存這個也需要在開發使用中去注意到的
實現原理其實很簡單,其實就是緩存管理和特定的銷毀和渲染邏輯,使得它不同于其他組件。
KeepAlive 組件在卸載組件時并不能真的將其卸載,而是將其放到一個隱藏的容器里面,當被激活時再從隱藏的容器中拿出來掛載到真正的 dom 上就行,這也就對應了 KeepAlive 的兩個獨特的生命周期activated
和deactivated
。
所以在 KeepAlive 內的子組件在 mount 和 unmount 的時候會執行特定的渲染邏輯,從而不會去走掛載和銷毀邏輯
const KeepAliveImpl: ComponentOptions = {
name: "KeepAlive",
// 標識這是一個 KeepAlive 組件
__isKeepAlive: true,
// props
props: {
exclude: [String, Array, RegExp],
include: [String, Array, RegExp],
max: [String, Number]
}
}
// isKeepAlive
export const isKeepAlive = (vnode: VNode): boolean =>
(vnode.type as any).__isKeepAlive
// setup 接著上面的代碼
// 獲取到當前 KeepAlive 組件實例
const instance = getCurrentInstance()! as any;
// 拿到 ctx
const sharedContext = instance.ctx as KeepAliveContext;
// cache 緩存
// key: vnode.key | vnode.type value: vnode
const cache: Cache = new Map()
// 需要拿到某些的 renderer 操作函數,需要自己特定執行渲染和卸載邏輯
const { renderer: { p: patch, m: move, um: _unmount, o: { createElement } } } = sharedContext
// 隱藏的容器,用來存儲需要隱藏的 dom
const storeageContainer = createElement('div')
// 存儲當前的子組件的緩存 key
let pendingKey: CacheKey | null = null
sharedContext.activate = (vnode, container, anchor) => {
// KeepAlive 下組件激活時執行的 move 邏輯
move(vnode, container, anchor, 0 /* ENTER */)
}
sharedContext.deactivate = (vnode) => {
// KeepAlive 下組件失活時執行的 move 邏輯
move(vnode, storeageContainer, null, 1 /* LEAVE */)
}
return () => {
// 沒有子組件
if (!slots.default) {
return null;
}
const children = slots.default() as VNode[];
const rawNode = children[0];
let vnode = rawNode;
const comp = vnode.type as ConcreteComponent;
const name = comp.displayName || comp.name
const { include, exclude } = props;
// 沒有命中的情況
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
// 直接渲染子組件
return rawNode;
}
// 獲取子組件的 vnode key
const key = vnode.key == null ? comp : vnode.key;
// 獲取子組件緩存的 vnode
const cachedVNode = cache.get(key);
pendingKey = key;
// 命中緩存
if (cachedVNode) {
vnode.el = cachedVNode.el;
// 繼承組件實例
vnode.component = cachedVNode.component;
// 在 vnode 上更新 shapeFlag,標記為 COMPONENT_KEPT_ALIVE 屬性,防止渲染器重新掛載
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
} else {
// 沒命中將其緩存
cache.set(pendingKey, vnode)
}
// 在 vnode 上更新 shapeFlag,標記為 COMPONENT_SHOULD_KEEP_ALIVE 屬性,防止渲染器將組件卸載了
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
// 渲染組件 vnode
return vnode;
}
在 KeepAlive 組件內會從 sharedContext 上的 renderer 上拿到一些方法比如 move、createElement 等
function mountComponent() {
// ...
if (isKeepAlive(initialVNode)) {
;(instance.ctx as KeepAliveContext).renderer = internals
}
}
首先從上面可以看到,在渲染 KeepAlive 組件時會對其子組件的 vnode 上增加對應的 shapeFlag 標志
比如COMPONENT_KEPT_ALIVE
標志,組件掛載的時候告訴渲染器這個不需要 mount 而需要特殊處理
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
) => {
if (n1 == null) {
// 在 KeepAlive 組件渲染時會對子組件增加 COMPONENT_KEPT_ALIVE 標志
// 掛載子組件時會判斷是否 COMPONENT_KEPT_ALIVE ,如果是不會調用 mountComponent 而是直接執行 activate 方法
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor
)
}
// ...
}
}
同理COMPONENT_SHOULD_KEEP_ALIVE
標志也是用來在組件卸載的時候告訴渲染器這個不需要 unmount 而需要特殊處理。
const unmount: UnmountFn = (vnode) => {
// ...
// 在 KeepAlive 組件渲染時會對子組件增加 COMPONENT_SHOULD_KEEP_ALIVE 標志
// 然后在子組件卸載時并不會真實的卸載而是調用 KeepAlive 的 deactivate 方法
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
return
}
}
activated
和deactivated
生命周期(生命周期相關可以不用重點看)首先這兩個生命周期是在 KeepAlive 組件內獨特聲明的,是直接導出使用的。
export function onActivated(
hook: Function,
target?: ComponentInternalInstance | null
) {
// 注冊 activated 的回調函數到當前的 instance 的鉤子函數上
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
}
export function onDeactivated(
hook: Function,
target?: ComponentInternalInstance | null
) {
// 注冊 deactivated 的回調函數到當前的 instance 的鉤子函數上
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
}
然后因為這兩個生命周期會注冊在 setup 里面,所以只要執行 setup 就會將兩個生命周期的回調函數注冊到當前的 instance 實例上
// renderer.ts
// mount 函數邏輯
const mountComponent = (initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// ...
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 執行 setup
setupComponent(instance)
}
// setupcomponent 處理 setup 函數值
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
// ...
const isStateful = isStatefulComponent(instance)
// ...
const setupResult = isStateful
// setupStatefulComponent 函數主要功能是設置當前的 instance
? setupStatefulComponent(instance, isSSR)
: undefined
// ...
}
function setupStatefulComponent(
instance: ComponentInternalInstance
){
if (setup) {
//設置當前實例
setCurrentInstance(instance)
// 執行組件內 setup 函數,執行 onActivated 鉤子函數進行回調函數收集
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
// currentInstance = null;
unsetCurrentInstance()
}
}
最后在執行sharedContext.activate
和sharedContext.deactivate
的時候將注冊在實例上的回調函數取出來直接執行就OK了,執行時機在 postRender 之后
sharedContext.activate = (vnode, container, anchor) => {
// KeepAlive 下組件激活時執行的 move 邏輯
move(vnode, container, anchor, 0 /* ENTER */)
// 把回調推入到 postFlush 的異步任務隊列中去執行
queuePostRenderEffect(() => {
if (instance.a) {
// a是 activated 鉤子的簡稱
invokeArrayFns(instance.a)
}
})
}
sharedContext.activate = (vnode, container, anchor) => {
// KeepAlive 下組件失活時執行的 move 邏輯
move(vnode, container, anchor, 0 /* ENTER */)
queuePostRenderEffect(() => {
if (instance.da) {
// da是 deactivated 鉤子的簡稱
invokeArrayFns(instance.da)
}
})
}
export const enum LifecycleHooks {
// ... 其他生命周期聲明
DEACTIVATED = 'da',
ACTIVATED = 'a',
}
export interface ComponentInternalInstance {
// ... 其他生命周期
[LifecycleHooks.ACTIVATED]: Function[]
[LifecycleHooks.DEACTIVATED]: Function[]
}
以下是關于上述demo如何實現的簡化流程圖
KeepAlive 組件的onMounted
和onUpdated
生命周期時進行緩存
緩存數量超過設置的 max 時
監聽 include 和 exclude 修改的時候,會讀取緩存中的知進行判斷是否需要清除緩存
修剪緩存的時候也要 unmount(如果該緩存不是當前組件)或者 resetShapeFlag 將標志為從 KeepAlive 相關 shapeFlag 狀態重置為 STATEFUL_COMPONENT 狀態(如果該緩存是當前組件,但是被exclude了),當然 unmount 函數內包含 resetShapeFlag 操作
KeepAlive 組件的緩存策略是 LRU(last recently used)緩存策略
核心思想在于需要把當前訪問或渲染的組件作為最新一次渲染的組件,并且該組件在緩存修剪過程中始終是安全的,即不會被修剪。
sharedContext.activate = (vnode, container, anchor) => {
// instance 是子組件實例
const instance = vnode.component!
// ...
// dev環境下設置, 自己模擬寫的
devtools.emit('component:added', instance.appContext.app, instance.uid, instance.parent ? instance.parent.uid: undefined, instance)
// 官方添加
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
// Update components tree
devtoolsComponentAdded(instance)
}
}
// 同理 sharedContext.deactivates 上也要添加,不然不會顯示在組件樹上
當子組件有 prop 更新時是需要重新去 patch 的,所以在 activate 的時候需要重新執行 patch 進行子組件更新
sharedContext.activate = (vnode, container, anchor) => {
// ...
// props 改變需要重新 patch(update)
patch(
instance.vnode,
vnode,
container,
anchor,
instance,
parentSuspense,
isSVG,
vnode.slotScopeIds,
optimized
)
}
到此,關于“Vue中的KeepAlive組件怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。