怎么做網(wǎng)站計劃寧波企業(yè)網(wǎng)站seo
目錄
- 說明
- 組件是如何被緩存的,什么時候被激活
- 對于KeepAlive 中組件 如何完成激活的
- 對于KeepAlive 中組件 如何完成休眠的
- 總結(jié)
說明
Vue 內(nèi)置了 KeepAlive 組件,實現(xiàn)緩存多個組件實例切換時,完成對卸載組件實例的緩存,從而使得組件實例在來會切換時不會被重復(fù)創(chuàng)建。
<template><KeepAlive> <component :is="xxx" /> </KeepAlive>
</template>
當(dāng)動態(tài)組件在隨著 xxx 變化時,如果沒有 KeepAlive 做緩存,那么組件在來回切換時就會進行重復(fù)的實例化,這里就是通過 KeepAlive 實現(xiàn)了對不活躍組件的緩存,只有第一次加載會初始化 instance,后續(xù)會使用 緩存的 vnode 再強制patch 下 防止遺漏 有 組件 props 導(dǎo)致的更新,省略了(初始化 instance 和 全量生成組件dom 結(jié)構(gòu)的過程)。
組件是如何被緩存的,什么時候被激活
先得看下 KeepAlive 的實現(xiàn) ,它本身是一個抽象組件,會將子組件渲染出來
const KeepAliveImpl = {// 組件名稱name: `KeepAlive`,// 區(qū)別于其他組件的標記__isKeepAlive: true,// 組件的 props 定義props: {include: [String, RegExp, Array],exclude: [String, RegExp, Array],max: [String, Number]},setup(props, {slots}) {// ...// setup 返回一個函數(shù) 就是 組件的render 函數(shù) return () => {// ...}}
每次 子組件改變 就會觸發(fā) render 函數(shù)
看下 render 函數(shù)的詳情
const KeepAliveImpl = {//...// cache sub tree after renderlet pendingCacheKey: CacheKey | null = nullconst cacheSubtree = () => {// fix #1621, the pendingCacheKey could be 0if (pendingCacheKey != null) {cache.set(pendingCacheKey, getInnerChild(instance.subTree))}}onMounted(cacheSubtree)onUpdated(cacheSubtree)onBeforeUnmount(() => {cache.forEach(cached => {const { subTree, suspense } = instanceconst vnode = getInnerChild(subTree)if (cached.type === vnode.type && cached.key === vnode.key) {// current instance will be unmounted as part of keep-alive's unmountresetShapeFlag(vnode)// but invoke its deactivated hook hereconst da = vnode.component!.dada && queuePostRenderEffect(da, suspense)return}unmount(cached)})})// ...setup(props, { slot }) {// ...return () => {// 記錄需要被緩存的 keypendingCacheKey = null// ...// 獲取子節(jié)點const children = slots.default()const rawVNode = children[0]if (children.length > 1) {// 子節(jié)點數(shù)量大于 1 個,不會進行緩存,直接返回current = nullreturn children} else if (!isVNode(rawVNode) ||(!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&!(rawVNode.shapeFlag & ShapeFlags.SUSPENSE))) {current = nullreturn rawVNode}// suspense 特殊處理,正常節(jié)點就是返回節(jié)點 vnodelet vnode = getInnerChild(rawVNode)const comp = vnode.type// 獲取 Component.name 值const name = getComponentName(isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp)// 獲取 props 中的屬性const { include, exclude, max } = props// 如果組件 name 不在 include 中或者存在于 exclude 中,則直接返回if ((include && (!name || !matches(include, name))) ||(exclude && name && matches(exclude, name))) {current = vnodereturn rawVNode}// 緩存相關(guān),定義緩存 keyconst key = vnode.key == null ? comp : vnode.key// 從緩存中取值const cachedVNode = cache.get(key)// clone vnode,因為需要重用if (vnode.el) {vnode = cloneVNode(vnode)if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {rawVNode.ssContent = vnode}}// 給 pendingCacheKey 賦值,將在 beforeMount/beforeUpdate 中被使用pendingCacheKey = key// 如果存在緩存的 vnode 元素if (cachedVNode) {// 復(fù)制掛載狀態(tài)// 復(fù)制 DOMvnode.el = cachedVNode.el// 復(fù)制 componentvnode.component = cachedVNode.component// 增加 shapeFlag 類型 COMPONENT_KEPT_ALIVEvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE// 把緩存的 key 移動到到隊首keys.delete(key)keys.add(key)} else {// 如果緩存不存在,則添加緩存keys.add(key)// 如果超出了最大的限制,則移除最早被緩存的值if (max && keys.size > parseInt(max as string, 10)) {pruneCacheEntry(keys.values().next().value)}}// 增加 shapeFlag 類型 COMPONENT_SHOULD_KEEP_ALIVE,避免被卸載vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVEcurrent = vnode// 返回 vnode 節(jié)點return isSuspense(rawVNode.type) ? rawVNode : vnode}}
}
props.max 會確定 緩存組件的最大數(shù)量 默認沒有上限,但是組件會占用內(nèi)存 所以并不是 max越大越好
props.include 表示包含哪些組件可被緩存
props.exclude 表示排除那些組件
組件緩存的時機:
組件切換的時候 會保存 上一個組件的vnode 到 cache Map 中 key 是 vnode.key
組件卸載時機:
緩存組件數(shù)量超過max,會刪除活躍度最低的緩存組件,或者 整個KeepAlive 組件被unmount的時候
對于KeepAlive 中組件 如何完成激活的
當(dāng) component 動態(tài)組件 is 參數(shù)發(fā)生改變時 ,
執(zhí)行 KeepAlive組件 componentUpdateFn 就會執(zhí)行 上一步的render 函數(shù) 會 生成 新的vnode (
然后再 走 patch
再走到 processComponent
再看下 processComponent中 針對 vnode.shapeFlag 為COMPONENT_KEPT_ALIVE(在keepalive render 函數(shù)中 組件類型 會被設(shè)置成COMPONENT_KEPT_ALIVE ) 有特殊處理
其中 parentComponent 其實指向的是 KeepAlive 組件, 得出 processComponent 實際調(diào)用的是 KeepAlive 組件上下文中的 activate 方法 去做掛載操作
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {const instance = vnode.component!// 先直接將 move(vnode, container, anchor, MoveType.ENTER, parentSuspense)// in case props have changedpatch(instance.vnode,vnode,container,anchor,instance,parentSuspense,isSVG,vnode.slotScopeIds,optimized)queuePostRenderEffect(() => {instance.isDeactivated = falseif (instance.a) {invokeArrayFns(instance.a)}const vnodeHook = vnode.props && vnode.props.onVnodeMountedif (vnodeHook) {invokeVNodeHook(vnodeHook, instance.parent, vnode)}}, parentSuspense)if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {// Update components treedevtoolsComponentAdded(instance)}}
先直接將緩存的dom 先掛載到 container 下面(節(jié)約了 重新生成dom的 時間 ),在強制patch 一下 避免遺漏 有props 改變引發(fā)的更新。這時候 緩存的組件就被激活了。
對于KeepAlive 中組件 如何完成休眠的
<template><KeepAlive> <component :is="xxx" /> </KeepAlive>
</template>
is 發(fā)生改變 會導(dǎo)致 上一次的組件執(zhí)行unmount 操作
const unmount = (vnode, parentComponent, parentSuspense, doRemove = false) => {// ...const { shapeFlag } = vnodeif (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)return}// ...
}
同理 也會走到。keepalive 上下文中的 deactivate 方法
sharedContext.deactivate = (vnode: VNode) => {const instance = vnode.component!move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)queuePostRenderEffect(() => {if (instance.da) {invokeArrayFns(instance.da)}const vnodeHook = vnode.props && vnode.props.onVnodeUnmountedif (vnodeHook) {invokeVNodeHook(vnodeHook, instance.parent, vnode)}instance.isDeactivated = true}, parentSuspense)if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {// Update components treedevtoolsComponentAdded(instance)}}
卸載態(tài)函數(shù) deactivate 核心工作就是將頁面中的 DOM 移動到一個隱藏不可見的容器 storageContainer 當(dāng)中,這樣頁面中的元素就被移除了。當(dāng)這一切都執(zhí)行完成后,最后再通過 queuePostRenderEffect 函數(shù),將用戶定義的 onDeactivated 鉤子放到狀態(tài)更新流程后
總結(jié)
1.組件是通過類似于 LRU 的緩存機制來緩存的,并為緩存的組件 vnode 的 shapeFlag 屬性打上 COMPONENT_KEPT_ALIVE 屬性,當(dāng)組件在 processComponent 掛載時,如果存在COMPONENT_KEPT_ALIVE 屬性,則會執(zhí)行激活函數(shù),激活函數(shù)內(nèi)執(zhí)行具體的緩存節(jié)點掛載邏輯。
2.緩存不是越多越好,因為所有的緩存節(jié)點都會被存在 cache 中,如果過多,則會增加內(nèi)存負擔(dān)。
3.丟棄的方式就是在緩存重新被激活時,之前緩存的 key 會被重新添加到隊首,標記為最近的一次緩存,如果緩存的實例數(shù)量即將超過指定的那個最大數(shù)量,則最久沒有被訪問的緩存實例將被丟棄。