В первой части этой серии мы говорили о том, как мы можем создать примитивную реактивную систему, преобразовав наши свойства данных в геттеры / сеттеры и сохранив зависимые вычисления в захваченном массиве. В этой части мы рассмотрим, как Vue применяет ту же идею для достижения своей реактивности под капотом.

В приведенном ниже фрагменте кода мы создаем экземпляр Vue с тремя свойствами данных, цена, количество и скидка и двумя вычисляемыми свойствами totalPrice и discountedPrice. Затем мы монтируем экземпляр в div с идентификатором app, чтобы отобразить страницу в браузере.

Давайте посмотрим, что происходит, когда мы создаем экземпляр Vue с помощью функции-конструктора. Он вызывает метод _init, определенный на прототипе Vue, со всеми нашими данными в параметре option. В нашем случае объект option будет выглядеть примерно так.

В приведенном ниже коде показана суть метода _init. Здесь выполняется вся важная инициализация. Что касается реактивности, мы сосредоточимся на initState () и $ mount (), как выделено.

vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
if (vm.$options.el) {
    vm.$mount(vm.$options.el);
}

Как мы видим, initState вызывает initData и initComputed. Основная цель initData - пройтись по всем свойствам (цена, количество и скидка) и определить для каждого реактивного геттера и сеттера, которые будут позже используется для сбора и уведомления о зависимостях. Это похоже на то, что мы обсуждали в первой части этой статьи. initData также передает каждое свойство в экземпляр Vue через прокси, чтобы к ним можно было получить доступ через this.

Затем initComputed перебирает все вычисленные свойства, в нашем случае totalPrice и discountedPrice, и создает наблюдателя Экземпляр для каждого. Мы можем рассматривать наблюдателей как инкапсуляцию фактических вычислений. Значение наблюдателя - это результат вычислений. Наблюдатель может быть создан как ленивый, в этом случае значение наблюдателя не будет оцениваться сразу. Наблюдатели за вычисляемыми свойствами создаются как ленивые наблюдатели.

Ключевым моментом наблюдателя является то, что он содержит вычисление, которое зависит от некоторого свойства данных. При вычислении наблюдателя любое свойство данных, к которому осуществляется доступ, может собирать наблюдателя как зависимость и сохранять его в локальном экземпляре dep, чтобы его можно было уведомить при изменении значения свойства.

Помимо всех наблюдателей, созданных для вычисленных свойств, создается специальный наблюдатель для вычислений, отвечающих за рендеринг страницы. Как видно из приведенного выше потока, следующим шагом после инициализации является процесс mount, который анализирует шаблон внутри нашего тега #app и создает функцию рендеринга, которая является анонимной функцией, возвращающей виртуальный узел DOM. Например, функция рендеринга простой метки выглядит так, как показано ниже:

> Vue.compile("<label>{{price}}</label>").render.toString()

"function anonymous( ) { with(this){return _c('label',[_v(_s(price))])} }"

Следующим шагом после запуска функции рендеринга и создания vnode является процесс исправления для рендеринга фактических элементов DOM. Оба этапа создания виртуальной DOM (vnode) из функции рендеринга и создания фактического DOM из vnode инкапсулируются в функцию с именем updateComponent, как показано ниже. Поскольку updateComponent - это вычисление, которое зависит от других элементов данных, для него создается новый наблюдатель, который называется наблюдателем рендеринга.

updateComponent = function () {
   vm._update(vm._render(), hydrating);
};
//Create the new watcher for updateComponent function 
new Watcher(vm, updateComponent, noop, {
    before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
                callHook(vm, 'beforeUpdate');
    }
 }
}, true /* isRenderWatcher */);

Наблюдатель рендеринга создается как нетерпеливый, что вызывает немедленное выполнение функции updateComponent. В процессе он использует значения наших вычисленных свойств (totalPrice и discountedPrice) и свойств данных (price и amount ), поскольку они используются для рендеринга нашей DOM. Если вы помните, наши наблюдатели вычисляемых свойств были созданы как ленивые, поэтому их также необходимо оценивать в процессе рендеринга. Поскольку все часы запускаются по очереди (наблюдатели рендеринга, а затем наблюдатели вычисляемых свойств), они будут собираться как зависимости, когда средства получения свойств данных вызываются во время их оценки. Важно помнить, что одновременно может работать только один наблюдатель.

В нашем примере свойство данных price будет иметь наблюдателей как для вычисляемых свойств, так и для наблюдателя рендеринга в своем списке зависимостей, и все три наблюдателя будут уведомлены о повторном запуске в определенном порядке, когда значение price изменяется через сеттер. Стоит отметить, что наблюдатель рендеринга обновит виртуальный элемент DOM, который будет сравниваться со старым виртуальным DOM во время процесса исправления, и только изменения будут применены к фактическому DOM, что делает процесс очень эффективным.

Вывод

В этой серии из двух частей мы обсудили, как Vue 2 реализует свою реактивность под капотом. В следующей версии Vue 3 вместо геттеров и сеттеров будут использоваться прокси-объекты ES6, которые будут рассмотрены в следующей статье.