feb-alive
Причина использования
- Разработчикам не нужно прописывать логику инициализации данных в разных хуках перед RouteUpdate или активировать из-за различий в динамической маршрутизации или общей маршрутизации.
- Разработчикам не нужно вручную кэшировать состояние страницы, например кэшировать данные текущей страницы через localStorage или sessionStorage.
- Feb-alive поможет вам обрабатывать хранение и восстановление метаинформации маршрута
Зачем разрабатывать feb-laive?
Когда мы разрабатываем проекты с помощью Vue, будут ли следующие требования к сценарию?
-
/aперенаправить на/b - вернуться к
/aкогда вы хотите восстановить страницу из кеша - снова прыгнуть в
/b, есть два случая- Случай 1: Перейти по ссылкам или нажать, вы хотите воссоздать
/bстраница вместо чтения из кеша - Случай 2: Если вы нажмете собственную кнопку браузера вперед, страница все равно будет считана из кеша.
- Случай 1: Перейти по ссылкам или нажать, вы хотите воссоздать
Требование этого сценария делает упор на кэширование.Преимущество кэширования заключается в том, что данные и статус моей последней страницы сохраняются, и нет необходимости извлекать данные с сервера, что значительно улучшает взаимодействие с пользователем.
Попробуйте реализовать кэширование страниц с помощью Keep-alive
<keep-alive>
<router-view></router-view>
</keep-alive>
так легко, но идеально идеально, реальность очень жестока
Существует проблема
-/aПрыгать/b, Прыгать в/a/a
- Точно так же переход динамической маршрутизации
/page/1->/page/2Поскольку две страницы ссылаются на один и тот же компонент, страница не изменится при переходе, потому что ключ кеша поддержки активности генерируется в соответствии с компонентом (конечно, Vue предоставляет нам хук beforeRouteUpdate для обновления данных). - Резюме: кэш проверки активности == уровень компонента ==, а не == уровень страницы ==.
Дайте сценарий применения
Например, просмотрите страницу статьи и посетите 3 статьи в свою очередь
- /artical/1
- /artical/2
- /artical/3
когда я из/artical/3вернуться к/artical/2В это время из-за кеша компонента страница по-прежнему содержит содержимое статьи 3, поэтому данные страницы 2 должны быть повторно загружены через beforeRouteUpdate. (Обратите внимание, что возвращение сюда не вызовет активированный хук компонента, потому что оба маршрута отображают один и тот же компонент, поэтому экземпляр будет использоваться повторно, а reactivateComponent не будет выполняться.)
если хочешь от/artical/3вернуться к/artical/2, при попытке восстановить предыдущую/artical/2некоторые государства в/artical/2Все данные о состоянии сохраняются и восстанавливаются.
Подводя итог: все еще существует разрыв между кешем на уровне компонентов, реализованным keep-alive, и кешем, который мы себе представляли, и keep-alive не может удовлетворить наши потребности.
==В ответ на эти проблемы родился плагин feb-alive==
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
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 () {
// 获取默认插槽
const slot = this.$slots.default
// 获取第一个组件,也就和官方说明的一样,keep-alive要求同时只有一个子元素被渲染,如果你在其中有 v-for 则不会工作。
const vnode: VNode = getFirstComponentChild(slot)
// 判断是否存在组件选项,也就是说只对组件有效,对于普通的元素则直接返回对应的vnode
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// 检测include和exclude
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
// 如果指定了子组件的key则使用,否则通过cid+tag生成一个key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
// 判断是否存在缓存
if (cache[key]) {
// 直接复用组件实例,并更新key的位置
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
// 此处存储的vnode还没有实例,在之后的流程中通过在createComponent中会生成实例
cache[key] = vnode
keys.push(key)
// 当缓存数量大于阈值时,删除最早的key
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 设置keepAlive属性,createComponent中会判断是否已经生成组件实例,如果是且keepAlive为true则会触发actived钩子。
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
Alive-Alive - это абстрактный компонент, а в экземпляре компонента поддерживается кеш, который является следующей частью кода
created () {
// 存储组件缓存
this.cache = Object.create(null)
this.keys = []
}
let Foo = {
template: '<div class="foo">foo component</div>',
name: 'Foo'
}
let Bar = {
template: '<div class="bar">bar component</div>',
name: 'Bar'
}
let gvm = new Vue({
el: '#app',
template: `
<div id="#app">
<keep-alive>
<component :is="renderCom"></component>
</keep-alive>
<button @click="change">切换组件</button>
</div>
`,
components: {
Foo,
Bar
},
data: {
renderCom: 'Foo'
},
methods: {
change () {
this.renderCom = this.renderCom === 'Foo' ? 'Bar': 'Foo'
}
}
})
В приведенном выше примере шаблон корневого экземпляра будет скомпилирован в следующую функцию рендеринга.
function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"#app"}},[_c('keep-alive',[_c(renderCom,{tag:"component"})],1),_c('button',{on:{"click":change}})],1)}
}
Доступна онлайн-компиляция:Talent.v ue js.org/v2/expensive/hot…
Согласно приведенной выше функции рендеринга, можно узнать, что процесс генерации vnode глубоко рекурсивен: сначала создается vnode дочернего элемента, а затем создается vnode родительского элемента. Таким образом, при первом рендеринге, когда генерируется vnode компонента поддержки активности, vnode компонента Foo был сгенерирован и передан в качестве параметра конструктора vnode компонента поддержки активности (_c).
_c('keep-alive',[_c(renderCom,{tag:"component"})
Vnode сгенерированного компонента для хранения живой является следующим образом
{
tag: 'vue-component-2-keep-alive',
...
children: undefined,
componentInstance: undefined,
componentOptions: {
Ctor: f VueComponent(options),
children: [Vnode],
listeners: undefined,
propsData: {},
tag: 'keep-alive'
},
context: Vue {...}, // 调用 $createElement/_c的组件实例, 此处是根组件实例对象
data: {
hook: {
init: f,
prepatch: f,
insert: f,
destroy: f
}
}
}
Здесь следует отметить, что Vnode компонента не имеет детей, но оригинальные дети используются в качестве дети свойства компонентов компонентов Vnode. Компоненты будут использоваться, когда компонент создается, а компоненты. Назначено VM. $ Слоты, часть исходного кода выглядит следующим образом
// createComponent函数
function createComponent (Ctor, data, context, children, tag) {
// 此处省略部分代码
...
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
return vnode
}
Vue, наконец, выполнит рендеринг с помощью функции patch, преобразует vnode в настоящий дом и будет рендерить компоненты с помощью createComponent.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
Следующие два шага
- Отрисовка самого компонента keep-alive
- keep-alive оборачивает рендеринг компонентов, в данном случае компонентов Foo и Bar.
Поговорим о рендеринге самого keep-alive компонента в этом примере
- создание экземпляра корневого компонента
- корневой компонент $mount
- Корневой компонент вызывает mountComponent
- Корневой компонент генерирует renderWatcher
- Корневой компонент вызывает updateComponent
- Корневой компонент вызывает vm.render() для создания vnode корневого компонента.
- Корневой компонент вызывает vm.update(vnode)
- Корневой компонент вызывает vm.patch(oldVnode, vnode)
- Координационный компонент вызывает Creteeeeeeeeever (Vnode)
- При рендеринге дочерних элементов, если они типа компонента, vnode вызывается createComponent (vnode), и именно в этом процессе были созданы подсборки и инстанцировано монтирование ($ mount)
createElm(keepAliveVnode)Примеры процесса и будут поддерживать компоненты монтирования, а в примере процесса инкапсулированный в подсборке узел поддержки активности будет назначен экземпляру компонента $ слота атрибута поддержки активности, поэтому сохраняйте при вызове функции. Например, слот can.$ для получения этой сборки пакета vnode, в демо-версии, сборка Foo vnode, специальная функция анализа рендеринга компонента keep-alive
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
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
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
Как было проанализировано выше, при выполненииcreateElm(keepAliveVnode)В ходе этого процесса будет выполняться создание и монтирование компонента поддержки активности.($mount), и в процессе монтирования будет выполняться функция рендеринга keep-alive. Как было проанализировано ранее, в функции рендеринга vnode подкомпонента может быть получен через this.$slot. Из приведенного выше исходного кода вы можете узнать что keep-alive обрабатывает только первый подкомпонент слота по умолчанию.Подразумевается, что если несколько компонентов заключены в keep-alive, остальные компоненты будут проигнорированы, например:
<keep-alive>
<Foo />
<Bar />
</keep-alive>
// 只会渲染Foo组件
Продолжить анализ. После получения vnode компонента Foo мы оцениваем componentOptions. Поскольку наш Foo является компонентом, здесь есть componentOptions. В логике if include означает, что будут кэшироваться только совпадающие компоненты, а exclude указывает, что любые совпадающие компоненты не будет кэшироваться, а в демо не заданы соответствующие правила, которые здесь будут проигнорированы.
const { cache, keys } = this
cache, ключи генерируются в хуке create компонента keep-alive и используются для хранения экземпляра компонента, кэшированного keep-alive, и ключа соответствующего vnode
created () {
this.cache = Object.create(null)
this.keys = []
}
продолжить ниже
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
Во-первых, выньте ключ vnode, если vnode.key существует, используйте vnode.key, если он не существует, используйте vnode.keycomponentOptions.Ctor.cid + (componentOptions.tag ? ::${componentOptions.tag} : '')В качестве ключа для хранения экземпляра компонента можно знать, что если мы не укажем ключ компонента, тот же компонент будет сопоставлен с тем же кешем, поэтому при описании подчеркивается, что это уровень компонента. keep-alive в начале схема кэширования.
Тогда при первом рендеринге кеш и ключи будут пусты, и здесь будет следовать логика else.
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
Сохраните vnode компонента Foo с ключом в качестве ключа кеша(注意此时vnode上面还没有componentInstance), здесь используется принцип хранения объектов, и тогда при инстанцировании компонента Foo его экземпляр будет присвоен vnode.componentInstance, тогда vnode.componentInstance можно будет получить при следующем рендеринге keep-alive компонента.
Таким образом, первый рендеринг происходит только в кэше поддержки активности, в котором хранится vnode, обертывающий компонент Foo.
Рендеринг для обернутых компонентов
В этом примере из-за изменения свойства renderCom сработает renderWatcher корневого компонента, а затем будет выполнен patch(oldVnode, vnode). При сравнении дочерних vnode старые и новые vnode keep-alive будут оценены как sameVnode, а затем войдет в логику patchVnode.
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) {
return
}
// 此处省略代码
...
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
// 此处省略代码
...
}
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
Можно увидеть, что примеры живой во время сборки корневого элемента будут повторно отображать мультиплексирование, что также гарантирует, что экземпляр компонента до того, как все еще существует в кеше памяти After Alive
var child = vnode.componentInstance = oldVnode.componentInstance;
последующийupdateChildComponentЭта функция очень критична, эта функция служит ключевой задачей переключения компонента Foo на компонент Bar. Мы знаем, что, поскольку компонент keep-alive используется здесь повторно, он больше не сработает.initRender, поэтому vm.$slot больше не будет обновляться. так вupdateChildComponentФункция берет на себя ответственность за обновление слота
function updateChildComponent (
vm,
propsData,
listeners,
parentVnode,
renderChildren
) {
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = true;
}
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
var hasChildren = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
);
// ...
// resolve slots + force update if has children
if (hasChildren) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context);
vm.$forceUpdate();
}
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = false;
}
}
Функция updateChildComponent в основном обновляет некоторые свойства текущего экземпляра компонента, включая props, listeners и slots. Давайте сосредоточимся на обновлении слота.Здесь последний vnode обернутого компонента получается через resolveSlots, который является компонентом Bar в демо, а затем компонент keep-alive принудительно перерисовывается через vm.$forceUpdate. (Советы: когда у нашего компонента есть слот, экземпляр компонента $fourceUpdate будет запущен при повторном рендеринге родительского компонента компонента. Здесь будет потеря производительности, потому что независимо от того, влияет ли изменение данных на слот , форс сработает. Обновите, судя по интродукции на vueConf, эта проблема будет оптимизирована в 3.0), например
// Home.vue
<template>
<Artical>
<Foo />
</Artical>
</tempalte>
В этом примере, когда компонент Home обновляется, он запускает принудительное обновление компонента Artical, и это обновление является избыточным.
Продолжить после обновления экземпляра keep-alive
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
// ...
}
Когда компонент переключается из бара в Foo снова
Логика для компонента keep-alive остается такой же, как описано выше.
- выполнить препатч
- Повторное использование экземпляров компонента проверки активности
- Выполните updateChildComponent для обновления $slots
- вызвать vm.$forceUpdate
- Запуск функции рендеринга компонента поддержки активности
Снова введите функцию рендеринга. На этот раз cache[key] будет соответствовать vnode, кэшированному при первом рендеринге компонента Foo. Посмотрите на эту часть логики.
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
Поскольку компонент, обернутый keep-alive, является компонентом Foo, согласно правилам, ключ, сгенерированный в это время, совпадает с ключом, сгенерированным при первом рендеринге компонента Foo, поэтому в этот раз входит функция рендеринга keep-alive. первая if branch , то есть совпадает с cache[key], присваивает закэшированный componentInstance текущему vnode, а затем обновляет ключи (когда есть max, можно гарантировать удаление более старого кэша).
Многие студенты могут спросить, каков эффект установки здесь vnode.componentInstance. Это включает в себя часть исходного кода vue.
Поскольку он переключается с компонента Bar на компонент Foo, при сравнении патча он не будет оцениваться как тот же Vnode, поэтому он, естественно, переходит к createElm.Поскольку Foo является компонентом Vue, он войдет в createComponent, поэтому в end Введите следующий фрагмент функции
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
Согласно анализу исходного кода keep-alive выше, здесь isReactivated имеет значение true, и тогда он войдет в функцию инициализации жизненного цикла, которая зависает при генерации vnode
var componentVNodeHooks = {
init: function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
},
...
}
В это время, поскольку экземпляр уже существует, и keepalive верно, первое, если будет предпринято логику, предводит, будет выполнено, свойства компонента и некоторые слушатели будут обновлены, если есть слот, слот также будет обновлен, а также $ ForceUpdate будет выполнена, это было проанализировано ранее, поэтому я не буду повторять его.
Продолжайте создавать компоненты, initComponent и вставка будут выполняться внутри функции.
if (isDef(vnode.componentInstance)) {
// 将实例上的dom赋值给vnode
initComponent(vnode, insertedVnodeQueue);
// 插入dom
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
До сих пор, когда компонент снова переключается с Bar на Foo, и экземпляр, и дом используются повторно, что обеспечивает высокий эффект опыта! И feb-alive, который мы реализуем позже, основан на keep-alive.
Решение Vue для кэширования на уровне страниц в феврале (ниже)