В последнем разделе упоминалось немного
VueСопутствующее содержимое встроенных компонентов, начиная с этого раздела, будет анализировать конкретный встроенный компонент. прежде всегоkeep-alive, это компонент, который часто используется в нашей повседневной разработке.Когда мы переключаемся между различными компонентами, нам часто требуется поддерживать состояние компонента, чтобы избежать потери производительности, вызванной повторным рендерингом компонента, иkeep-aliveЧасто используется вместе с динамическими компонентами, описанными в предыдущем разделе. Из-за слишком большого содержания,keep-aliveАнализ исходного кода будет разделен на верхнюю и нижнюю части, этот раздел в основном посвященkeep-aliveПервая визуализация разворота.
13.1 Основное использование
keep-aliveИспользование нужно только для добавления метки к самому внешнему слою динамического компонента.
<div id="app">
<button @click="changeTabs('child1')">child1</button>
<button @click="changeTabs('child2')">child2</button>
<keep-alive>
<component :is="chooseTabs">
</component>
</keep-alive>
</div>
var child1 = {
template: '<div><button @click="add">add</button><p>{{num}}</p></div>',
data() {
return {
num: 1
}
},
methods: {
add() {
this.num++
}
},
}
var child2 = {
template: '<div>child2</div>'
}
var vm = new Vue({
el: '#app',
components: {
child1,
child2,
},
data() {
return {
chooseTabs: 'child1',
}
},
methods: {
changeTabs(tab) {
this.chooseTabs = tab;
}
}
})
Простой результат таков: динамическая составляющая находится вchild1,child2переключаться вперед и назад, когда второй переключатель наchild1час,child1сохраняет исходное состояние данных,num = 5.
13.2 От компиляции шаблона до генерации vnode
По опыту предыдущего анализа начнем с анализа шаблонов.Первый вопрос: Есть ли разница между встроенными компонентами и обычными компонентами в процессе компиляции? Ответ - нет, будь то встроенный или определяемый пользователем компонент, по сути компонент компилируется в шаблонrenderМетод обработки функции такой же. Детали здесь не анализируются. Если у вас есть какие-либо сомнения, вы можете обратиться к основному анализу в предыдущих разделах. В конечном счетеkeep-aliveизrenderРезультат функции следующий:
with(this){···_c('keep-alive',{attrs:{"include":"child2"}},[_c(chooseTabs,{tag:"component"})],1)}
имеютrenderфункция, то генерация будет выполняться от дочерней к родительскойVnodeобъектный процесс,_c('keep-alive'···)обработка, выполнитcreateElementСоздание компонентовVnode, что обусловленоkeep-aliveявляется компонентом, поэтому он вызоветcreateComponentфункция для создания дочерних компонентовVnode,createComponentЭто также было проанализировано ранее, эта связь и создание общих компонентовVnodeРазница в том,keep-aliveизVnodeИзбыточное содержимое атрибута будет удалено.так какkeep-aliveКромеslotВ дополнение к свойствам внутри компонента не имеют значения другие свойства, такие какclassстиль,<keep-alive clas="test"></keep-alive>подождите, так что вVnodeСлоям имеет смысл отбрасывать лишние атрибуты. и<keep-alive slot="test">Метод записи также устарел в версиях выше 2.6.(вabstractКак признак абстрактного компонента, и о его роли поговорим позже)
// 创建子组件Vnode过程
function createComponent(Ctordata,context,children,tag) {
// abstract是内置组件(抽象组件)的标志
if (isTrue(Ctor.options.abstract)) {
// 只保留slot属性,其他标签属性都被移除,在vnode对象上不再存在
var slot = data.slot;
data = {};
if (slot) {
data.slot = slot;
}
}
}
13.3 Первоначальный рендеринг
keep-aliveОн особенный, потому что он не рендерит один и тот же компонент снова и снова, он просто использует кеш, оставшийся от первого рендеринга, для обновления узла. Итак, чтобы полностью понять принцип его реализации, нам нужно начать сkeep-aliveПервый рендер .
13.3.1 Блок-схема
Чтобы прояснить процесс, я примерно нарисовал блок-схему, которая примерно охватывает первоначальный рендеринг.keep-aliveВыполняемый процесс, анализ исходного кода будет выполняться в соответствии с этим процессом.
То же, что и при рендеринге обычных компонентов,Vueполучит ранее сгенерированныйVnodeОбъект выполняет процесс создания реального узла, также известный какpatchпроцесс,patchЭтап выполнения вызоветcreateElmсоздать настоящийdom, на пути к созданию узла,keep-aliveизvnodeОбъект будет считаться компонентомVnode, поэтому для компонентаVnodeбудет выполняться сноваcreateComponentфункционировать, это будетkeep-aliveКомпоненты инициализируются и создаются.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
// isReactivated用来判断组件是否缓存。
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
// 执行组件初始化的内部钩子 init
i(vnode, false /* hydrating */);
}
if (isDef(vnode.componentInstance)) {
// 其中一个作用是保留真实dom到vnode中
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
keep-aliveКомпонент сначала вызовет внутренний хукinitметод для инициализации, давайте сначала посмотримinitЧто дал процесс.
// 组件内部钩子
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 {
// 将组件实例赋值给vnode的componentInstance属性
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
// 后面分析
prepatch: function() {}
}
При первом исполнении видно, что компонентvnodeнетcomponentInstanceАтрибуты,vnode.data.keepAliveтакже не имеет значения, поэтому будетперечислитьcreateComponentInstanceForVnodeметод для создания экземпляра компонента и назначения экземпляра компонентаvnodeизcomponentInstanceАтрибуты,Окончательный экземпляр исполнительного компонента$mountспособ монтирования экземпляра.
createComponentInstanceForVnodeЭто процесс создания компонентов, а создание компонентов началось с первой части серии, это не что иное, как серия слияний опций, событий инициализации, жизненного цикла и других операций инициализации.
function createComponentInstanceForVnode (vnode, parent) {
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
// 内联模板的处理,忽略这部分代码
···
// 执行vue子组件实例化
return new vnode.componentOptions.Ctor(options)
}
13.3.2 Параметры встроенных компонентов
Когда мы используем компоненты, мы часто определяем параметры компонента в виде объектов, в том числеdata,method,computedи т. д., и зарегистрируйтесь в родительском или корневом компоненте.keep-aliveТакже следуйте этому принципу, встроенные два слова также объясняютkeep-aliveвVueКонфигурация встроенных опций в исходном коде также зарегистрирована глобально, на исходный код этой части можно ссылатьсяУглубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?Введение во встроенные конструкторы компонентов и процесс регистрации в конце раздела. В этой части мы сосредоточимся наkeep-aliveконкретные варианты.
// keepalive组件选项
var KeepAlive = {
name: 'keep-alive',
// 抽象组件的标志
abstract: true,
// keep-alive允许使用的props
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created: function created () {
// 缓存组件vnode
this.cache = Object.create(null);
// 缓存组件名
this.keys = [];
},
destroyed: function destroyed () {
for (var key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted: function mounted () {
var this$1 = this;
// 动态include和exclude
// 对include exclue的监听
this.$watch('include', function (val) {
pruneCache(this$1, function (name) { return matches(val, name); });
});
this.$watch('exclude', function (val) {
pruneCache(this$1, function (name) { return !matches(val, name); });
});
},
// keep-alive的渲染函数
render: function render () {
// 拿到keep-alive下插槽的值
var slot = this.$slots.default;
// 第一个vnode节点
var vnode = getFirstComponentChild(slot);
// 拿到第一个组件实例
var componentOptions = vnode && vnode.componentOptions;
// keep-alive的第一个子组件实例存在
if (componentOptions) {
// check pattern
//拿到第一个vnode节点的name
var name = getComponentName(componentOptions);
var ref = this;
var include = ref.include;
var exclude = ref.exclude;
// 通过判断子组件是否满足缓存匹配
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
var ref$1 = this;
var cache = ref$1.cache;
var keys = ref$1.keys;
var key = vnode.key == null
? 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);
} else {
// 初次渲染时,将vnode缓存
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;
}
// 将渲染的vnode返回
return vnode || (slot && slot[0])
}
};
keep-aliveПараметры в основном аналогичны параметрам компонентов, которые мы обычно пишем, с той лишь разницей, чтоkeep-ailveкомпоненты бесполезныtemplateвместо этого используйтеrenderфункция.keep-aliveПо сути, это просто процесс хранения и извлечения кеша, и фактического рендеринга узлов нет, поэтому используйтеrenderОбработка - лучший вариант.
13.3.3 Кэширование vnodes
Вернемся сначала к анализу блок-схемы. сказано вышеkeep-aliveКомпонент монтируется после создания экземпляра компонента. во время монтажа$mountназад сноваvm._render(),vm._update()процесс. так какkeep-aliveимеютrenderфункции, поэтому мы можем сосредоточиться непосредственно наrenderреализация функции.
-
- Во-первых, чтобы получить
keep-aliveсодержимое нижнего слота, т.е.keep-aliveДочерний компонент, который необходимо отрисовать, в примере этоchil1 Vnodeобъект, соответствующий исходному кодуgetFirstComponentChildфункция
- Во-первых, чтобы получить
function getFirstComponentChild (children) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
var c = children[i];
// 组件实例存在,则返回,理论上返回第一个组件vnode
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
-
- Судя по тому, что компонент удовлетворяет условиям соответствия кэша, в
keep-aliveВо время использования компонентаVueНам разрешено использовать исходный кодinclude, excludeопределить условия соответствия,includeУказывает, что кэшироваться будут только компоненты с совпадающими именами.excludeУказывает, что любые компоненты, чьи имена совпадают, не будут кэшироваться. В качестве альтернативы мы можем использоватьmaxЧтобы ограничить количество совпадающих экземпляров, которые можно кэшировать, зачем ограничивать число? Мы упомянем об этом позже.
- Судя по тому, что компонент удовлетворяет условиям соответствия кэша, в
После получения экземпляра подкомпонента нам нужно сначала решить, выполняются ли условия сопоставления,Правила сопоставления позволяют использовать массивы, строки и обычные формы.
var include = ref.include;
var exclude = ref.exclude;
// 通过判断子组件是否满足缓存匹配
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
// matches
function matches (pattern, name) {
// 允许使用数组['child1', 'child2']
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
// 允许使用字符串 child1,child2
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
// 允许使用正则 /^child{1,2}$/g
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
Если компонент не соответствует требованиям кеша, он напрямую вернет компонентvnode, не делайте никакой обработки, в это время компонент войдет в обычную ссылку для монтажа.
-
-
renderВажным шагом в выполнении функции является кэширование.vnode, так как это первое выполнениеrenderфункция, в опцияхcacheиkeysданные не имеют значения, гдеcacheэто пустой объект, который мы будем использовать для кэширования{ name: vnode }перечисление, при этомkeysМы используем для кэширования имен компонентов.Итак, мы рендерим в первый разkeep-aliveКогда дочерний компонент, который необходимо отобразить, будетvnodeкеш.
-
cache[key] = vnode;
keys.push(key);
-
- будет кэшироваться
vnodeотметьте и поместите субкомпонентVnodeвозвращение.vnode.data.keepAlive = true
- будет кэшироваться
13.3.4 Сохранение реальных узлов
мы возвращаемсяcreateComponentлогика, упомянутая ранееcreateComponentбудет выполняться первымkeep-aliveПроцесс инициализации компонентов также включает в себя монтирование подкомпонентов. и мы проходимcomponentInstanceпонятноkeep-aliveэкземпляр компонента, а следующийВажным шагом является установка реальногоdomсохранить сноваvnodeсередина.
function createComponent(vnode, insertedVnodeQueue) {
···
if (isDef(vnode.componentInstance)) {
// 其中一个作用是保留真实dom到vnode中
initComponent(vnode, insertedVnodeQueue);
// 将真实节点添加到父节点中
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
insertИсходный код не указан, это просто операция вызоваdomизapi, вставьте дочерний узел в родительский узел, мы можем сосредоточиться наinitComponentЛогика ключевых шагов.
function initComponent() {
···
// vnode保留真实节点
vnode.elm = vnode.componentInstance.$el;
···
}
Поэтому мы явно возвращаемся к оставленному ранее вопросу, почемуkeep-aliveнужен одинmaxограничить количество кэшируемых компонентов. Причина в том,keep-aliveКэшированные данные компонентов в дополнение к включениюvnodeКроме этого объекта описания существует еще реальныйdomузлов, а мы знаем, что настоящие объекты узлов огромны, поэтому хранение большого количества компонентов кэша требует высокой производительности. Поэтому нам необходимо строго контролировать количество кэшируемых компонентов, а также нам необходимо оптимизировать стратегию кэширования, о которой мы продолжим упоминать в следующей статье.
так какisReactivatedзаfalse,reactivateComponentФункция также не будет выполняться. ужеkeep-aliveАнализ начального процесса рендеринга завершен.
Если анализ шагов игнорируется, делается только сводка по начальному процессу рендеринга: встроенныйkeep-aliveкомпонент, так что дочерний компонент будетvnodeи настоящийelmкэшировано.
13.4 Абстрактные компоненты
В конце этого раздела вскользь упоминается упомянутая выше концепция абстрактных компонентов.VueЕсли встроенные компоненты имеют параметр, описывающий тип компонента, этот параметр{ astract: true }, что указывает на то, что компонент является абстрактным компонентом. Что такое абстрактный компонент и почему этот тип различия? Я думаю, что это сводится к двум причинам.
-
- Абстрактные компоненты не имеют реальных узлов, они не будут разобраны и преобразованы в реальные на этапе рендеринга компонента.
domузел, но только как промежуточный уровень передачи данных, вkeep-aliveЭто обработка кэша компонентов.
- Абстрактные компоненты не имеют реальных узлов, они не будут разобраны и преобразованы в реальные на этапе рендеринга компонента.
-
- Когда мы представили инициализацию компонентов, мы упомянули, что компоненты родитель-потомок явно устанавливают уровень отношений, который закладывает основу для связи между компонентами родитель-потомок. мы можем оглянуться назад снова
initLifecycleкод.
- Когда мы представили инициализацию компонентов, мы упомянули, что компоненты родитель-потомок явно устанавливают уровень отношений, который закладывает основу для связи между компонентами родитель-потомок. мы можем оглянуться назад снова
Vue.prototype._init = function() {
···
var vm = this;
initLifecycle(vm)
}
function initLifecycle (vm) {
var options = vm.$options;
var parent = options.parent;
if (parent && !options.abstract) {
// 如果有abstract属性,一直往上层寻找,直到不是抽象组件
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
···
}
Дочерний компонент смонтирует родительский экземпляр в свою собственную опцию на этапе регистрации.parentсвойства, вinitLifecycleВ процессе вы получите обратноеparentродительский компонент включенvnode, и для$childrenсвойство для добавления дочернего компонентаvnode, Если в процессе нахождения родительского компонента в обратном порядке родительский компонентabstractатрибут, вы можете определить, что компонент является абстрактным компонентом, в это время используйтеparentЦепочка ищет до тех пор, пока компонент не станет абстрактным компонентом.initLifecycleобработки, чтобы каждый компонент мог найти родительский компонент верхнего уровня и дочерний компонент нижнего уровня, так что между компонентами формируется тесное дерево взаимосвязей.
При первой обработке кеша, когда компонент рендерится во второй раз,
keep-aliveЧто за магия будет, и какие оптимизации кеша остались до этого? Они будут раскрыты один за другим в следующем подразделе.
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Углубленный анализ исходного кода Vue — основа компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)