> 웹 프론트엔드 > View.js > Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사

Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사

青灯夜游
풀어 주다: 2022-11-14 20:21:13
앞으로
1891명이 탐색했습니다.

Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사

최근 Vue 관련 지식 포인트를 읽다가 KeepAlive 컴포넌트를 보다가 컴포넌트 간 전환 시 어떻게 다시 렌더링되지 않는지 궁금해서 자세히 살펴봤습니다. (학습 동영상 공유: vue 동영상 튜토리얼)

특정 내부 구현이 어떻게 구현되는지 알고 싶거나 어느 정도 이해하고 있지만 충분히 익숙하지 않은 경우 함께 통합할 수도 있습니다

팁: 이런 면접, 이 지식 포인트를 다른 사람에게 큰소리로 물어볼 수 있는 때는 언제일까요?

KeepAlive가 무엇인가요?

KeepAlive 기능

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

KeepAlive 功能

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

// 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>
로그인 후 복사

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

所以为了解决这种性能开销,你需要知道是时候使用 KeepAlive 组件。

<KeepAlive>
  <component :is="curTab === &#39;Test&#39; ? Test : HelloWorld" :msg="curTab"></component>
</KeepAlive>
<div @click="toggle">toggle</div>
로그인 후 복사

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

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

如何实现

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

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

Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사

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

Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사所以在 KeepAlive 内的子组件在 mount 和 unmount 的时候会执行特定的渲染逻辑,从而不会去走挂载和销毁逻辑

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

  • 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
로그인 후 복사
  • KeepAlive 组件的 setup 逻辑以及渲染逻辑(重点看)

// 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组件 mount 时挂载 renderer 到 ctx 上

在 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
  }
}
로그인 후 복사
  • 如何挂载activateddeactivated

    KeepAlive는 HTTP 프로토콜에서 빌려온 것입니다. HTTP 프로토콜에서 KeepAlive는 영구 연결이라고도 합니다. 동일한 HTTP 연결을 공유하여 HTTP 연결의 빈번한 파괴 및 생성으로 인한 추가 성능 오버헤드를 해결합니다. 같은 방식으로 Vue의 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)
    }
    로그인 후 복사
    로그인 후 복사
위 코드를 보면 토글을 자주 클릭하면 Test/HelloWorld 컴포넌트가 자주 렌더링된다는 것을 알 수 있습니다. 사용자가 자주 클릭하면 테스트 컴포넌트가 자주 삭제/렌더링되어야 하므로 큰 손실이 발생합니다. 렌더링 성능에서.

그러므로 이러한 성능 오버헤드를 해결하려면 이제 KeepAlive 구성 요소를 사용해야 할 때라는 것을 알아야 합니다. 🎜
// 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()
  }
}
로그인 후 복사
로그인 후 복사
🎜🎜🎜보실 수 있습니다 이 화면을 기록할 때 첫 번째 로딩 후 빈번한 전환은 해당 구성 요소를 삭제하고 다시 마운트하지 않으며 렌더링할 때 다시 마운트하지 않고 다시 활성화하면 됩니다. 규모가 크면 성능 최적화가 잘 될 수 있습니다. 🎜🎜경험해보고 싶다면 이 예시를 살펴보세요.공식 데모🎜 , 개발 및 사용 시 캐싱에도 주의가 필요합니다🎜

🎜구현 방법🎜🎜🎜실제로 구현 원리는 매우 간단합니다. 🎜캐시 관리와 특정 파괴 및 렌더링 로직🎜으로 다른 구성 요소와 다릅니다. 🎜🎜KeepAlive 구성 요소는 🎜제거할 때 실제로 제거할 수 없습니다. 대신 숨겨진 컨테이너에 배치됩니다🎜. 활성화되면 숨겨진 컨테이너에서 꺼내어 실제 dom에 마운트됩니다. KeepAlive 활성화비활성화의 두 가지 고유한 수명 주기에 적용됩니다. 🎜🎜Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사🎜

🎜먼저 컴포넌트 마운트 과정을 간략하게 알아보겠습니다🎜🎜🎜Vue의 KeepAlive 구성 요소에 대해 이야기하는 기사그래서 KeepAlive의 🎜 하위 구성 요소는 마운트 및 마운트 해제 시 특정 렌더링 로직을 실행하므로 마운트 및 파괴 로직이 수행되지 않습니다. 🎜🎜

🎜구체적인 구현(작고 간단한 KeepAlive 구현)🎜🎜

원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿