Введение
Оригинальная ссылка: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:
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функция.