Heim  >  Artikel  >  Web-Frontend  >  Ein Artikel über die KeepAlive-Komponente in Vue

Ein Artikel über die KeepAlive-Komponente in Vue

青灯夜游
青灯夜游nach vorne
2022-11-14 20:21:131838Durchsuche

Ein Artikel über die KeepAlive-Komponente in Vue

Kürzlich habe ich über Vue-bezogene Wissenspunkte gelesen. Als ich die KeepAlive-Komponente sah, war ich neugierig, dass sie beim Wechseln zwischen Komponenten nicht neu gerendert wird, also habe ich mir das genauer angesehen. (Lernvideo-Sharing: vue-Video-Tutorial)

Wenn Sie auch daran interessiert sind, zu erfahren, wie die konkrete interne Umsetzung umgesetzt wird oder Sie ein gewisses Verständnis haben, sich aber nicht ausreichend auskennen, dann können Sie es auch gemeinsam festigen

Tipps: Interview wie dieses Dann können Sie andere lautstark nach diesem Wissenspunkt fragen?

Was ist KeepAlive?

KeepAlive-Funktion ist dem HTTP-Protokoll entlehnt. Im HTTP-Protokoll wird KeepAlive auch als dauerhafte Verbindung bezeichnet. Seine Funktion besteht darin, mehrere Anfragen/Antworten zu ermöglichen Verwenden Sie dieselbe HTTP-Verbindung, wodurch der zusätzliche Leistungsaufwand behoben wird, der durch die häufige Zerstörung und Erstellung von HTTP-Verbindungen entsteht. Auf die gleiche Weise ist die KeepAlive-Komponente in Vue auch darauf ausgelegt, zu verhindern, dass eine Komponente häufig zerstört/neu erstellt wird, und so Leistungseinbußen zu vermeiden.

// 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>
<keepalive></keepalive> 是一个内置组件,它的功能是在多个组件间动态切换缓存被移除的组件实例。

KeepAlive 功能

KeepAlive 一词借鉴于 HTTP 协议,在 HTTP 协议里面 KeepAlive 又称持久连接,作用是允许多个请求/响应共用同一个 HTTP 连接,解决了频繁的销毁和创建 HTTP 连接带来的额外性能开销。而同理 Vue 里的 KeepAlive 组件也是为了避免一个组件被频繁的销毁/重建,避免了性能上的开销。

<KeepAlive>
  <component :is="curTab === &#39;Test&#39; ? Test : HelloWorld" :msg="curTab"></component>
</KeepAlive>
<div @click="toggle">toggle</div>

上述代码可以看到,如果我们频繁点击 toggle 时会频繁的渲染 Test/HelloWorld 组件,当用户频繁的点击时 Test 组件需要频繁的销毁/渲染,这就造成很大的渲染性能损失。

所以为了解决这种性能开销,你需要知道是时候使用 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

可以看这个录屏,在首次加载后再次频繁的切换并没有重新销毁与挂载,而仅仅是将组件进行了失活(而不是销毁),渲染时只需要重新激活就可以,而不需重新挂载,如果要渲染的组件很大,那就能有不错的性能优化。

想要体验的话可以去看看这个例子?官方demo,其中数据会被缓存这个也需要在开发使用中去注意到的

如何实现

实现原理其实很简单,其实就是缓存管理和特定的销毁和渲染逻辑,使得它不同于其他组件。

KeepAlive 组件在卸载组件时并不能真的将其卸载,而是将其放到一个隐藏的容器里面当被激活时再从隐藏的容器中拿出来挂载到真正的 dom 上就行,这也就对应了 KeepAlive 的两个独特的生命周期activateddeactivated

Ein Artikel über die KeepAlive-Komponente in Vue

先来简单了解下组件的挂载过程

Ein Artikel über die KeepAlive-Komponente in Vue所以在 KeepAlive 内的子组件在 mount 和 unmount 的时候会执行特定的渲染逻辑,从而不会去走挂载和销毁逻辑

具体实现(实现一个小而简单的 KeepAlive)

  • KeepAlive 组件的属性

// 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;
}
  • KeepAlive 组件的 setup 逻辑以及渲染逻辑(重点看)

function mountComponent() {
 // ...
 if (isKeepAlive(initialVNode)) {
    ;(instance.ctx as KeepAliveContext).renderer = internals
  }
}
  • KeepAlive组件 mount 时挂载 renderer 到 ctx 上

在 KeepAlive 组件内会从 sharedContext 上的 renderer 上拿到一些方法比如 move、createElement 等

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
        )
      }
      // ...
    }
  }
  • 子组件执行特定的销毁和渲染逻辑

首先从上面可以看到,在渲染 KeepAlive 组件时会对其子组件的 vnode 上增加对应的 shapeFlag 标志

比如COMPONENT_KEPT_ALIVE标志,组件挂载的时候告诉渲染器这个不需要 mount 而需要特殊处理

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
  }
}

同理COMPONENT_SHOULD_KEEP_ALIVE标志也是用来在组件卸载的时候告诉渲染器这个不需要 unmount 而需要特殊处理。

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)
}
  • 如何挂载activateddeactivatedSie können dem obigen Code entnehmen, dass die Test-/HelloWorld-Komponente häufig gerendert wird, wenn wir häufig auf „Umschalten“ klicken. Wenn der Benutzer häufig klickt, muss die Testkomponente häufig zerstört/gerendert werden, was zu großen Verlusten führt bei der Rendering-Leistung.

  • Um diesen Leistungsaufwand zu beheben, müssen Sie wissen, dass es an der Zeit ist, die KeepAlive-Komponente zu verwenden.
// 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()
  }
}

🎜Sie können zuschauen Bei der Aufnahme wird der Bildschirm durch häufiges Umschalten nach dem ersten Laden nicht zerstört und erneut gemountet, sondern nur deaktiviert (anstatt zerstört). Beim Rendern muss es nur dann erneut aktiviert werden, wenn die Komponente erneut gerendert werden soll groß, kann es gute Leistungsoptimierungen geben. 🎜🎜Wenn Sie es erleben möchten, können Sie sich dieses Beispiel ansehen?Offizielle Demo🎜 , in dem die Daten gespeichert werden, muss bei der Entwicklung und Verwendung ebenfalls beachtet werden🎜

🎜Wie man es implementiert🎜🎜🎜Das Implementierungsprinzip ist eigentlich sehr einfach. Es handelt sich um „Cache-Verwaltung und spezifische Zerstörungs- und Rendering-Logik“, wodurch es sich von anderen Komponenten unterscheidet. 🎜🎜KeepAlive-Komponente kann beim 🎜Deinstallieren der Komponente nicht wirklich deinstalliert werden🎜 Bei Aktivierung wird sie aus dem versteckten Container entnommen und auf dem echten Dom montiert 🎜, das entspricht auch zu den beiden einzigartigen Lebenszyklen von KeepAlive aktiviert und deaktiviert. 🎜🎜Ein Artikel über die KeepAlive-Komponente in Vue🎜

🎜Lassen Sie uns zunächst kurz den Komponentenmontageprozess verstehen🎜🎜🎜Ein Artikel über die KeepAlive-Komponente in VueDie 🎜-Unterkomponente in KeepAlive führt also beim Mounten und Unmounten eine bestimmte Rendering-Logik aus, sodass die Mount- und Zerstörungslogik nicht ausgeführt wird 🎜🎜🎜Konkrete Implementierung (Implementierung eines kleinen und einfachen KeepAlive)🎜🎜

Das obige ist der detaillierte Inhalt vonEin Artikel über die KeepAlive-Komponente in Vue. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen