принцип реализации keep-alive

JavaScript

В сочетании со следующими случаями

<keep-alive>
    <coma v-if="visible"></coma>
    <comb v-else></comb>
</keep-alive>
<button @click="visible = !visible">更改</button>

Например, вcomaа такжеcombИмеетсяinputиметь соответствующиеvalue, если мы неkeep-alive, при сменеvisible, оба компонента будут перерисованы, ранее введенный контент будет потерян, и будет выполнен полный процесс жизненного цикла:beforeCreate => created....
Но если мы используемkeep-alive, то на следующем переключенииvisibleкогда,inputсоответствующийvalueзначение на момент последнего изменения. такkeep-aliveОн в основном используется для поддержания состояния компонента и предотвращения повторного создания компонента.

принцип

keep-aliveСпособ использования указан вcore/components/keep-aliveсередина

export default {
    abstract: true,
    props: {
        include: patternTypes, // 缓存白名单
        exclude: patternTypes,  // 缓存黑名单
        max: [String, Number] // 缓存的实例上限
    },
    created() {
        // 用于缓存虚拟DOM
        this.cache = Object.create(null);
        this.keys = [];
    },
    mounted() {
    // 用于监听i黑白名单,如果发生调用pruneCache
    // pruneCache更新vue的cache缓存
        this.$watch('include', val => {
            pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name))
        })
    }
    render() {
        //...
    }
}

Вышеприведенный код определяет операции нескольких циклов объявлений, наиболее важныхrenderфункция, давайте посмотрим, как она реализована

render

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. согласно сvnodeизcidа такжеtagСгенерированоkey, есть ли текущий кеш в объекте кеша, если да, вернуть и обновитьkeyсуществуетkeysрасположение в
  4. Если для текущего кэшированного объекта нет кеша, перейдите кcacheдобавить содержимое этого, и в соответствии сLRUАлгоритм удаляет экземпляры, которые в последнее время не использовались
  5. Установите для первого дочернего объекта компонентаkeep-aliveдляtrue

первый рендер

В сочетании со статьей в начале статьи для анализа текущего примера, когда страница отображается в первый раз, поскольку процесс рендеринга компонента - это сначала дочерний компонент, а затем родительский компонент, данные дочернего компонента можно получить здесь, а затем здесь можно получить данные дочернего компонента.vnodeинформация хранится вcache, и положиcomaкомпонентkeepAliveустановлен вtrue. Это вопрос, почему я могу получить подкомпоненты?componentOptions, с помощью приведенного выше примера мы знаем, что генерацияvnodeчерезrenderфункция,renderфункция передается вplatforms/web/entry-runtime-with-compilerопределено вcompileToFunctionsБудуtemplateкомпилируется вrenderфункция, посмотрите на сгенерированную корреспонденциюrenderфункция

<template>
    <div class="parent">
        <keep-alive>
            <coma v-if="visible"></coma>
        <comb v-else></comb>
        </keep-alive>
    </div>
</template>
<script>
(function anonymous() {
  with(this) {
    return _c('div', {
      staticClass: "parent"
    }, [
      _c('keep-alive', [(visibility) ? _c('coma') : _c('comb')], 1), 
      _c('button', {
      on: {
        "click": change
      }
    }, [_v("change")])], 1)
  }
})
</script>

можно увидеть сгенерированныйrenderсвязанные с функциейkeep-aliveпроцесс генерации

 _c('keep-alive', [(visibility) ? _c('coma') : _c('comb')], 1),

существуетkeep-aliveпозвонил первым_c('coma'), так что вы можете получить доступ к подкомпонентамcomponentOptions,специфический_cвvdom/create-element.jsОпределенный в , он считает, что он должен генерировать компонентыvnodeили что-то другое.

Изменятьdata,вызыватьpatch

На первом рендере мы меняемcomaсерединаinputзначение, см. когдаvisibleизменить снова наtrueкогда,inputБудет ли запоминаться предыдущее значение. потому что изменилосьvisibleзначение, код будет выполнен повторно

updateComponent = () => {
    vm._update(vm._render())
}

Так что это будет выполнено повторноkeep-aliveизrenderфункцию, потому что данные уже сохранены вcache, так что на этот раз данные напрямую изcacheбыть казненным.

vnode.componentInstance = cache[key].componentInstance

Когда он был впервые представлен, было упомянуто, что когдаkeyЕсли значение не существует, подкомпонентvnodeКэшируется, если вы можете увидеть первый рендер, разбив точкуcomponentInstanceдляundefined,componentInstanceпо фактуpatchвызов компонента в процессеinitХук только генерируется, так почему я могу получить его в это время? Вот пример для объяснения. Например, есть следующие примеры

a = {
    b: 1
}
c = a;
a.b = 5;
console.log(c.b) // 5

objectЭто ссылочный тип, поэтому при изменении исходного объекта ссылочное место также изменится.
Затем переназначьте предыдущую информацию о состоянииcoma, то зачем присваиватьcoma,comaне будет выполнять процесс создания компонента, см.patchкод, при выполнении которогоcreateComponentкогда, потому чтоcomaДля компонента будет выполняться логика, связанная с компонентом

// 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);
        }
    }
}
// core/vdom/create-component
init(vnode) {
    if (vnode.componentInstance && 
        !vnode.componentInstance._isDetroyed &&
        vnode.data.keepAlive) {
            const mountedNode: any = node;
            componentVnodeHooks.prepatch(mountedNode, mountedNode)
    } else {
        const child = vnode.componentInstance = createComponentInstanceForVnode(
            vnode,
            activeInstance
        )
        child.$mount(vnode.elm)
    }
}

потому чтоvnode.componentInstanceсуществуетkeep-aliveбыл переназначен, поэтому иkeepAliveдляtrue, поэтому выполняйте толькоprepatch,такcreated,mounted

keep-alivepatch

core/instance/renderupdateComponent

updateComponent = () => {
    vm._update(vm._render())
}

keep-aliverendervnodevm._updatepatchkeep-alivepatch

keep-alivepatchcore/vdom/patch

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 */)
        }
    }
}

componentInstanceinitinit
keep-alivecore/instance/lifecycle$childrenvm._init

let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

patch

visiblekeep-alivepatchdataupdateComponent

updateComponent = () => {
    vm._update(vm._render())
}

keep-aliverenderpatchkeep-aliveprepatch

tickvnode

keep-alive

keep-alivekeep-alive


  1. keep-alive

  2. keep-alivekeep-aliveactivated
    1. cache

Vuegithub