Rumah  >  Artikel  >  hujung hadapan web  >  Artikel untuk bercakap tentang komponen KeepAlive dalam Vue

Artikel untuk bercakap tentang komponen KeepAlive dalam Vue

青灯夜游
青灯夜游ke hadapan
2022-11-14 20:21:131856semak imbas

Artikel untuk bercakap tentang komponen KeepAlive dalam Vue

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?

Apa itu KeepAlive

<keepalive></keepalive> ialah komponen terbina dalam, fungsinya untuk bertukar secara dinamik antara berbilang komponen apabila Cacheketika komponen dialih keluar.

Fungsi KeepAlive

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 === &#39;Test&#39;"></Test>
<HelloWorld :msg="curTab" v-if="curTab === &#39;HelloWorld&#39;"></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 === &#39;Test&#39; ? 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

Cara melaksanakan

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.

Artikel untuk bercakap tentang komponen KeepAlive dalam Vue

Mula-mula mari kita fahami secara ringkas proses pemasangan komponen

Artikel untuk bercakap tentang komponen KeepAlive dalam VueJadi sub-komponen dalam pemaparan Spesifik KeepAlive logik akan dilaksanakan semasa memasang dan menyahlekap, supaya logik pemasangan dan pemusnahan tidak akan melalui

Pelaksanaan khusus (melaksanakan KeepAlive yang kecil dan ringkas)

  • Sifat komponen KeepAlive

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
  • Logik persediaan komponen KeepAlive dan logik pemaparan (fokus)

// 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(&#39;div&#39;)

// 存储当前的子组件的缓存 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;
}
  • Apabila komponen KeepAlive dipasang, pemapar dipasang pada ctx

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
  }
}
  • Subkomponen melaksanakan pemusnahan khusus dan logik rendering

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
  }
}
  • Cara memasang kitaran hayat 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.activatesharedContext.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 = &#39;da&#39;,
  ACTIVATED = &#39;a&#39;,
}
export interface ComponentInternalInstance {
// ... 其他生命周期
[LifecycleHooks.ACTIVATED]: Function[]
[LifecycleHooks.DEACTIVATED]: Function[]
}

以下是关于上述demo如何实现的简化流程图

Artikel untuk bercakap tentang komponen KeepAlive dalam Vue

需要注意的知识点

1、什么时候缓存

KeepAlive 组件的onMountedonUpdated生命周期时进行缓存

2、什么时候取消缓存

  • 缓存数量超过设置的 max 时

  • 监听 include 和 exclude 修改的时候,会读取缓存中的知进行判断是否需要清除缓存

修剪缓存的时候也要 unmount(如果该缓存不是当前组件)或者 resetShapeFlag 将标志为从 KeepAlive 相关 shapeFlag 状态重置为 STATEFUL_COMPONENT 状态(如果该缓存是当前组件,但是被exclude了),当然 unmount 函数内包含 resetShapeFlag 操作

3、缓存策略

KeepAlive 组件的缓存策略是 LRU(last recently used)缓存策略

核心思想在于需要把当前访问或渲染的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。

看下面的图更加直观,图片来源一篇讲keepAlive 缓存优化的文章

4、如何添加到 vue devtools 组件树上

sharedContext.activate = (vnode, container, anchor) => {
  // instance 是子组件实例
  const instance = vnode.component!
  // ...
  // dev环境下设置, 自己模拟写的
  devtools.emit(&#39;component:added&#39;, instance.appContext.app, instance.uid, instance.parent ? instance.parent.uid: undefined, instance)
  // 官方添加
  if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    // Update components tree
    devtoolsComponentAdded(instance)
  }
}
// 同理 sharedContext.deactivates 上也要添加,不然不会显示在组件树上

5、缓存的子组件 props 更新处理

当子组件有 prop 更新时是需要重新去 patch 的,所以在 activate 的时候需要重新执行 patch 进行子组件更新

sharedContext.activate = (vnode, container, anchor) => {
  // ...
  // props 改变需要重新 patch(update)
  patch(
    instance.vnode,
    vnode,
    container,
    anchor,
    instance,
    parentSuspense,
    isSVG,
    vnode.slotScopeIds,
    optimized
  )
}

(学习视频分享:web前端开发编程基础视频

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!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam