Baru-baru ini saya melihat titik pengetahuan berkaitan Vue Apabila saya melihat komponen KeepAlive, saya ingin tahu bagaimana ia tidak dipaparkan semula apabila bertukar antara komponen, jadi saya mengambil melihat lebih dekat. (Belajar perkongsian video: tutorial video vue)
Jika anda juga berminat untuk mengetahui bagaimana pelaksanaan dalaman khusus dilaksanakan atau anda mempunyai pemahaman tertentu tetapi tidak cukup biasa, maka anda boleh sertai kami. Consolidate
Petua: Dengan cara ini, anda boleh bertanya kepada orang lain tentang titik pengetahuan ini dengan lantang semasa temu duga?
<keepalive></keepalive>
ialah komponen terbina dalam, fungsinya untuk bertukar secara dinamik antara berbilang komponen apabila Cacheketika komponen dialih keluar.
Perkataan KeepAlive dipinjam daripada protokol HTTP Dalam protokol HTTP, KeepAlive juga dipanggil sambungan berterusan /responses untuk berkongsi Sambungan HTTP yang sama menyelesaikan overhed prestasi tambahan yang disebabkan oleh pemusnahan yang kerap dan penciptaan sambungan HTTP. Dengan cara yang sama, komponen KeepAlive dalam Vue juga direka untuk menghalang komponen daripada dimusnahkan/dibina semula dengan kerap dan mengelakkan overhed prestasi.
// App.vue <Test :msg="curTab" v-if="curTab === 'Test'"></Test> <HelloWorld :msg="curTab" v-if="curTab === 'HelloWorld'"></HelloWorld> <div @click="toggle">toggle</div>
Seperti yang anda lihat daripada kod di atas, jika kami mengklik togol dengan kerap, komponen Test/HelloWorld akan kerap dipaparkan Apabila pengguna mengklik dengan kerap, komponen Ujian perlu dimusnahkan/diberikan dengan kerap , yang menyebabkan banyak masalah menyebabkan kehilangan prestasi.
Jadi untuk menyelesaikan overhed prestasi ini, anda perlu tahu bahawa sudah tiba masanya untuk menggunakan komponen KeepAlive.
<KeepAlive> <component :is="curTab === 'Test' ? Test : HelloWorld" :msg="curTab"></component> </KeepAlive> <div @click="toggle">toggle</div>
Anda boleh menonton rakaman skrin ini Selepas pemuatan pertama, penukaran yang kerap tidak musnah dan melekap semula, tetapi hanya menyahaktifkan komponen ( Daripada memusnahkannya) , anda hanya perlu mengaktifkannya semula semasa membuat, tanpa melekapkannya semula Jika komponen yang akan dipaparkan adalah besar, anda boleh mempunyai pengoptimuman prestasi yang baik.
Jika anda ingin mengalaminya, anda boleh lihat contoh ini?Demo rasmi, di mana data akan dicache, yang juga perlu diperhatikan semasa pembangunan dan penggunaan
Prinsip pelaksanaan sebenarnya sangat mudah pengurusan cache dan pemusnahan dan logik rendering khusus, yang menjadikannya berbeza. daripada komponen lain.
Komponen KeepAlive tidak boleh benar-benar menyahpasangnya apabila menyahpasang komponen, tetapi meletakkannya ke dalam bekas tersembunyi dan mengeluarkannya daripada bekas tersembunyi apabila ia diaktifkan Hanya keluarkan dan lekapkannya pada dom sebenar , yang sepadan dengan dua kitaran hayat unik KeepAlive activated
dan deactivated
.
Jadi sub-komponen dalam pemaparan Spesifik KeepAlive logik akan dilaksanakan semasa memasang dan menyahlekap, supaya logik pemasangan dan pemusnahan tidak akan melalui
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; }
Dalam komponen KeepAlive, ia akan dimuatkan daripada sharedContext Get beberapa kaedah pada pemapar seperti move, createElement, dsb.
function mountComponent() { // ... if (isKeepAlive(initialVNode)) { ;(instance.ctx as KeepAliveContext).renderer = internals } }
Pertama daripada semua, mulakan dari atas Ia boleh dilihat bahawa apabila memaparkan komponen KeepAlive, bendera shapeFlag yang sepadan
akan ditambahkan pada vnod subkomponen Contohnya, COMPONENT_KEPT_ALIVE
bendera memberitahu pemapar apabila komponen dilekapkan Memerlukan pelekapan tetapi memerlukan rawatan khas
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 ) } // ... } }
Begitu juga, bendera COMPONENT_SHOULD_KEEP_ALIVE
juga digunakan untuk memberitahu pemapar apabila komponen dinyahlekapkan bahawa ia tidak dipasang. memerlukan unmount tetapi memerlukan rawatan khas.
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
dan deactivated
(anda tidak perlu fokus pada kitaran hayat yang berkaitan) Pertama sekali, kedua-dua kitaran hayat ini diisytiharkan secara unik dalam komponen KeepAlive dan dieksport dan digunakan secara langsung.
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 时
修剪缓存的时候也要 unmount(如果该缓存不是当前组件)或者 resetShapeFlag 将标志为从 KeepAlive 相关 shapeFlag 状态重置为 STATEFUL_COMPONENT 状态(如果该缓存是当前组件,但是被exclude了),当然 unmount 函数内包含 resetShapeFlag 操作
KeepAlive 组件的缓存策略是 LRU(last recently used)缓存策略
核心思想在于需要把当前访问或渲染的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。
看下面的图更加直观,图片来源一篇讲keepAlive 缓存优化的文章
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 ) }
Atas ialah kandungan terperinci Artikel untuk bercakap tentang komponen KeepAlive dalam Vue. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!