Тщательно раскрыть принцип keep-alive

Vue.js
Тщательно раскрыть принцип keep-alive

Введение

Оригинальная ссылка:github.com/qi...

Содержание, представленное в этой статье, включает в себя:

  • постоянное использование: динамические компоненты и vue-router
  • анализ исходного кода keep-alive
  • Хуки для компонентов keep-alive и их обернутых компонентов
  • Визуализация компонентов поддержки активности и их обернутых компонентов

2. Введение и применение keep-alive

2.1 Что такое Keep-alive

keep-alive является абстрактным компонентом: он не отображает элемент DOM сам по себе и не появляется в цепочке родительских компонентов; обертывание динамических компонентов с помощью keep-alive кэширует экземпляры неактивных компонентов вместо их уничтожения.

2.2 Сцена

Пользователь выбирает условие фильтра на странице списка, чтобы отфильтровать список данных, переходит на страницу сведений о данных со страницы списка, а затем возвращается на страницу списка. Мы надеемся, что страница списка сможет сохранить фильтр пользователя (или выбранный) государство. keep-alive используется для решения этого сценария. Конечно, keep-alive — это не только простое сохранение состояния страниц/компонентов, но и возможность избежать повторного создания и рендеринга компонентов, эффективно повышая производительность системы. Как правило, keep-alive используется для сохранения состояния рендеринга компонента.

2.3 использование поддержания активности

  • Применение в динамических компонентах
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
  <component :is="currentComponent"></component>
</keep-alive>
  • Приложение в vue-router
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
  <router-view></router-view>
</keep-alive>

includeОпределите белый список кеша, keep-alive будет кэшировать компоненты попадания;excludeОпределите черный список кеша, хитовые компоненты не будут кэшироваться;maxОпределите верхний предел компонента кэша, используйте за пределами верхнего пределастратегия LRUЗамените данные кеша.

Три, анализ исходного кода

keep-alive.jsЕсть также некоторые служебные функции, определенные внутри.Мы удерживаем таблицу и смотрим на объекты, которые она предоставляет.

// src/core/components/keep-alive.js
export default {
  name: 'keep-alive',
  abstract: true, // 判断当前组件虚拟dom是否渲染成真是dom的关键

  props: {
    include: patternTypes, // 缓存白名单
    exclude: patternTypes, // 缓存黑名单
    max: [String, Number] // 缓存的组件实例数量上限
  },

  created () {
    this.cache = Object.create(null) // 缓存虚拟dom
    this.keys = [] // 缓存的虚拟dom的健集合
  },

  destroyed () {
    for (const key in this.cache) { // 删除所有的缓存
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // 实时监听黑白名单的变动
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    // 先省略...
  }
}

Видно, что, как и в процессе определения компонентов, мы сначала задаем имя компонентаkeep-alive, а затем определитьabstractсвойство, значениеtrue. Это свойство не упоминается в официальном руководстве по vue, но оно очень важно и будет использоваться в последующем процессе рендеринга.propsАтрибут определяет все параметры, поддерживаемые компонентом проверки активности.

keep-alive определяет три функции-ловушки в своем жизненном цикле:

  • created

    Инициализируйте два объекта для кэширования наборов ключей, соответствующих VNode (виртуальный DOM) и VNode соответственно.

  • destroyed

    Удалитьthis.cacheЭкземпляр VNode кэшируется в . Отметим, что это не простоthis.cacheустановлен вnull, но повторяет вызовpruneCacheEntryфункция удалить.

// src/core/components/keep-alive.js
function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy() // 执行组件的destory钩子函数
  }
  cache[key] = null
  remove(keys, key)
}

Удаление кэшированного VNode также соответствует экземпляру исполняемого компонента.destoryфункция крючка.

  • mounted

    существуетmountedЭтот крючок правильныйincludeиexcludeПараметры отслеживаются, а затем обновляются (удаляются) в режиме реального времени.this.cacheданные объекта.pruneCacheСуть функции заключается в вызовеpruneCacheEntry.

  • render

  // src/core/components/keep-alive.js
  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot) // 找到第一个子组件对象
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) { // 存在组件参数
      // check pattern
      const name: ?string = getComponentName(componentOptions) // 组件名
      const { include, exclude } = this
      if ( // 条件匹配
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      const key: ?string = vnode.key == null // 定义组件的缓存key
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      if (cache[key]) { // 已经缓存过该组件
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key) // 调整key排序
      } else {
        cache[key] = vnode // 缓存组件对象
        keys.push(key)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到
    }
    return vnode || (slot && slot[0])
  }
  • Шаг 1: Получите первый объект подкомпонента, обернутый keep-alive и его имя компонента;
  • Шаг 2: Выполните условное сопоставление в соответствии с установленным черным и белым списком (если есть), чтобы решить, нужно ли кэшировать. Если не совпадает, вернуть экземпляр компонента (VNode) напрямую, в противном случае выполнить третий шаг;
  • Шаг 3: Создайте ключ кэша в соответствии с идентификатором компонента и теги и узнайте, что экземпляр компонента был кэширован в объекте кэша. Если есть, удалите значение кэша и обновите его.keyсуществуетthis.keys(ключом к реализации стратегии замены LRU является обновление позиции ключа), в противном случае выполняется четвертый шаг;
  • Шаг 4: вthis.cacheЭкземпляр компонента хранится в объекте и сохраняетсяkeyзначение, затем проверьте, не превышает ли количество кэшированных экземпляровmaxЕсли значение превышено, последний неиспользованный экземпляр (то есть ключ с нижним индексом 0) будет удален в соответствии с политикой замены LRU.
  • Шаг пятый: Наконец, что очень важно, экземпляр компонентаkeepAliveЗначение свойства установлено наtrue. Это в @Не игнорируйте: функции хуковГлавы появятся снова.

В-четвертых, главное событие: рендеринг

4.1 Процесс рендеринга Vue

Сделайте снимок, чтобы увидеть весь процесс рендеринга Vue:

Рендеринг Vue взят из рисункаrenderЭтап начинается, но рендеринг поддержки активности находится на этапе исправления, который представляет собой процесс построения дерева компонентов (виртуального дерева DOM) и преобразования VNodes в реальные узлы DOM.

Краткое описание отrenderприбытьpatchпроцесс

Начнем с самого простогоnew VueНачинать:

import App from './App.vue'

new Vue({
  render: h => h(App),
}).$mount('#app')
  • Vue сначала вызывает прототип на прототипе при рендеринге_renderФункция преобразует объект компонента в экземпляр VNode; и_renderпозвонивcreateElementиcreateEmptyVNodeПреобразуются две функции;
  • createElementПроцесс преобразования будет выбран в соответствии с различными ситуациямиnew VNodeили позвоните по телефонуcreateComponentФункция делает создание Vnode;
  • После завершения создания vNode на этот раз Vue вызывает прототипы._updateФункция отображает VNode как настоящий DOM, и этот процесс выполняется путем вызова__patch__Функция завершена (это фаза пути)

Выразите это на графике:

4.2 Визуализация компонентов поддержки активности

Мы использовали keep-alive и знаем, что он не генерирует реальных узлов DOM, как это делается?

// src/core/instance/lifecycle.js
export function initLifecycle (vm: Component) {
  const options = vm.$options
  // 找到第一个非abstract的父组件实例
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  vm.$parent = parent
  // ...
}

Когда Vue инициализирует жизненный цикл, он устанавливает отношения родитель-потомок для экземпляра компонента в соответствии сabstractСвойство определяет, следует ли игнорировать компонент. В keep-alive установитьabstract: true, то Vue пропустит экземпляр компонента.

Окончательно построенное дерево компонентов не будет содержать компонентов поддержания активности, поэтому дерево DOM, отображаемое деревом компонентов, естественно, не будет иметь узлов, связанных с поддержанием активности.

Как компоненты поддержки активности используют кэширование?

существуетpatchэтап, будет выполнятьcreateComponentфункция:

// src/core/vdom/patch.js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }

      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm) // 将缓存的DOM(vnode.elm)插入父元素中
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }
  • Когда обернутый компонент загружается в первый раз,keep-alive.jsсерединаrenderфункция видна,vnode.componentInstanceЗначениеundefined,keepAliveЗначениеtrue, так как компонент проверки активности действует как родительский компонент, егоrenderФункция будет выполняться перед обернутым компонентом, затем она будет выполняться только до тех пор, покаi(vnode, false /* hydrating */)логика больше не выполняется;
  • При повторном доступе к обернутому компонентуvnode.componentInstanceЗначением является экземпляр компонента, который был кэширован, после чего он будет выполняться.insert(parentElm, vnode.elm, refElm)Логика, так что последний DOM прямо вставляется в родительский элемент.

Пять, нельзя игнорировать: функция крюка

5.1 Хуки, которые выполняются только один раз

Для общих компонентов каждая загрузка будет иметь полный жизненный цикл, то есть будут срабатывать соответствующие функции-хуки в жизненном цикле.Почему компоненты, обернутые keep-alive, нет? мы в@Анализ исходного кодаВ главе анализируется, что для него будет установлен кешированный экземпляр компонента.keepAlive = true, и в функции хука компонента инициализации:

// src/core/vdom/create-component.js
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }
  // ...
}

Видно, что когдаvnode.componentInstanceиkeepAliveКогда значение истинно, в то же время больше не вводить$mountпроцесс, чтоmountedВсе предыдущие функции ловушек (beforeCreate,created,mounted) больше не выполняются.

5.2 Повторяемость активирована

существуетpatchэтап, который в конечном итоге будет выполненinvokeInsertHookфункция, и эта функция должна вызывать сам экземпляр компонента (VNode)insertкрюк:

// src/core/vdom/patch.js
  function invokeInsertHook (vnode, queue, initial) {
    if (isTrue(initial) && isDef(vnode.parent)) {
      vnode.parent.data.pendingInsert = queue
    } else {
      for (let i = 0; i < queue.length; ++i) {
        queue[i].data.hook.insert(queue[i])  // 调用VNode自身的insert钩子函数
      }
    }
  }

посмотри сноваinsertкрюк:

// src/core/vdom/create-component.js
const componentVNodeHooks = {
  // init()
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  // ...
}

Внутри этого крючка позвонитеactivateChildComponentФункция рекурсивно выполняет все подкомпоненты.activatedФункция крючка:

// src/core/instance/lifecycle.js
export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

Наоборот,deactivatedФункция ловушки работает по тому же принципу в экземпляре компонента (VNode).destroyВызывается в функции ловушкиdeactivateChildComponentфункция.

Ссылаться на