Подробная реализация жизненного цикла Vue

Vue.js

предисловие

Когда мы обычно используем различные фреймворки, мы не можем избежать использования одной функции, а именноЖизненный циклХуки, эти хуки, могут предоставить нам массу удобства, позволяя фиксировать изменения в данных на каждом этапе их обновления.

Главное, о чем мы говорим, этоvueЖизненный цикл , сначала приходит с планом:

  • beforeCreate (перед инициализацией интерфейса)
  • создан (после инициализации интерфейса)
  • beforeMount (перед рендерингом dom)
  • смонтирован (после рендеринга дома)
  • beforeUpdate (перед обновлением данных)
  • обновлено (после обновления данных)
  • beforeDestroy (перед удалением компонента)
  • уничтожен (после удаления компонента)

Сегодня буду анализироватьvueЧто именно вы делаете перед вызовом каждого жизненного цикла?

текст

Давайте взглянем на официальную блок-схему жизненного цикла:

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

beforeCreate (перед инициализацией интерфейса)

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  // doing this because it's faster than dynamic enumeration.
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;
  opts._parentElm = options._parentElm;
  opts._refElm = options._refElm;

  var vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}
function resolveConstructorOptions (Ctor) {
  var options = Ctor.options;
  if (Ctor.super) {
    var superOptions = resolveConstructorOptions(Ctor.super);
    var cachedSuperOptions = Ctor.superOptions;
    if (superOptions !== cachedSuperOptions) {
      // super 选项已更改,需要解决新选项。
      Ctor.superOptions = superOptions;
      // 检查是否有任何后期修改/附加选项
      var modifiedOptions = resolveModifiedOptions(Ctor);
      // 更新基本扩展选项
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions);
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
      if (options.name) {
        options.components[options.name] = Ctor;
      }
    }
  }
  return options
}
if (options && options._isComponent) {
  initInternalComponent(vm, options);
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  );
}

if (process.env.NODE_ENV !== 'production') {
  initProxy(vm);
} else {
  vm._renderProxy = vm;
}

vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');

Вначале сначала выполняется процесс объединения атрибутов, еслиoptionsсуществует и_isComponentдляtrue, затем позвонитеinitInternalComponentметод, этот метод в основном предназначен для оптимизации создания экземпляров внутренних компонентов, поскольку динамическое слияние параметров происходит очень медленно, и никакие параметры внутренних компонентов не требуют специальной обработки;

Если вышеуказанные условия не соблюдены, звонитеmergeOptionsМетод выполняет слияние атрибутов, и окончательное возвращаемое значение присваивается$options,mergeOptionsПринцип реализацииАнализ исходного кода Vue (перед созданием экземпляра) — инициализация глобального API (1)Я сделал подробное объяснение здесь.Для тех, кто этого не знает, вы можете перейти сюда, чтобы увидеть это;

Сделайте перехват рендеринга, перехват здесь в основном для вызоваrenderметод, поvm.$createElementметодdomсоздание;

function initLifecycle (vm) {
  var options = vm.$options;

  // 找到第一个非抽象父级
  var parent = options.parent;
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent;
    }
    parent.$children.push(vm);
  }

  vm.$parent = parent;
  vm.$root = parent ? parent.$root : vm;

  vm.$children = [];
  vm.$refs = {};

  vm._watcher = null;
  vm._inactive = null;
  vm._directInactive = false;
  vm._isMounted = false;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;
}

Некоторые параметры инициализируются;

function initEvents (vm) {
  vm._events = Object.create(null);
  vm._hasHookEvent = false;
  // init父级附加事件
  var listeners = vm.$options._parentListeners;
  if (listeners) {
    updateComponentListeners(vm, listeners);
  }
}
function updateComponentListeners (
  vm,
  listeners,
  oldListeners
) {
  target = vm;
  updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
  target = undefined;
}

событие инициализации, если_parentListenersЕсли он существует, обновите прослушиватель событий компонента;

function initRender (vm) {
  vm._vnode = null; // 子树的根
  vm._staticTrees = null; // v-once缓存的树
  var options = vm.$options;
  var parentVnode = vm.$vnode = options._parentVnode; // 父树中的占位符节点
  var renderContext = parentVnode && parentVnode.context;
  vm.$slots = resolveSlots(options._renderChildren, renderContext);
  vm.$scopedSlots = emptyObject;
  // 将createElement fn绑定到此实例,以便我们在其中获得适当的渲染上下文。
  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
  // 规范化始终应用于公共版本,在用户编写的渲染函数中使用。
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

  // 暴露了$ attrs和$ listeners以便更容易创建HOC。
  // 他们需要被动反应,以便使用它们的HOC始终更新
  var parentData = parentVnode && parentVnode.data;

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
      !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
    }, true);
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
      !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
    }, true);
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true);
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true);
  }
}

инициализировать рендеринг,defineReactiveиспользование и рольАнализ исходного кода Vue (перед созданием экземпляра) — принцип реализации адаптивных данныхЗдесь есть пояснения, все желающие могут ознакомиться;

После того, как выполнение здесь завершено, оно вызываетсяbeforeCreateметод.

создан (после инициализации интерфейса)

initInjections(vm); // 在数据/道具之前解决注入
initState(vm);
initProvide(vm); // 解决后提供的数据/道具
callHook(vm, 'created');
function resolveInject (inject, vm) {
  if (inject) {
    // 因为流量不足以弄清楚缓存
    var result = Object.create(null);
    var keys = hasSymbol
      ? Reflect.ownKeys(inject).filter(function (key) {
        return Object.getOwnPropertyDescriptor(inject, key).enumerable
      })
      : Object.keys(inject);

    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var provideKey = inject[key].from;
      var source = vm;
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey];
          break
        }
        source = source.$parent;
      }
      if (!source) {
        if ('default' in inject[key]) {
          var provideDefault = inject[key].default;
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault;
        } else if (process.env.NODE_ENV !== 'production') {
          warn(("Injection \"" + key + "\" not found"), vm);
        }
      }
    }
    return result
  }
}
var shouldObserve = true;

function toggleObserving (value) {
  shouldObserve = value;
}
function initInjections (vm) {
  var result = resolveInject(vm.$options.inject, vm);
  if (result) {
    toggleObserving(false);
    Object.keys(result).forEach(function (key) {
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], function () {
          warn(
            "Avoid mutating an injected value directly since the changes will be " +
            "overwritten whenever the provided component re-renders. " +
            "injection being mutated: \"" + key + "\"",
            vm
          );
        });
      } else {
        defineReactive(vm, key, result[key]);
      }
    });
    toggleObserving(true);
  }
}

Здесь, собственно, самое главное — сделать данные, не требующие отзывчивости.Официальные документы:provide / inject;

function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

после обработкиinjectПосле этого он это сделает.props,methods,data,computedа такжеwatchобработка инициализации;

function initProvide (vm) {
  var provide = vm.$options.provide;
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide;
  }
}

Provideа такжеInjectФункция на самом деле такая же, но метод обработки другой.Чтобы узнать о конкретных различиях, обратитесь к официальному документу:provide / inject;

После того, как выполнение здесь будет завершено, необходимо перейти кcreatedмонтировать.

beforeMount (перед рендерингом dom)

if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}

рендерингdom, сначала проверьте, есть ли позиция рендеринга, если нет, то она не будет зарегистрирована;

Vue.prototype.$mount = function (
  el,
  hydrating
) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};
function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
  }
  callHook(vm, 'beforeMount');
}

существуетbeforeMountЗдесь в принципе ничего не делается, простоrenderПривязать метод, если он существуетcreateEmptyVNodeфункция;

После привязки выполнитьbeforeMountкрюк;

смонтирован (после рендеринга дома)

  var updateComponent;
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var vnode = vm._render();
      mark(endTag);
      measure(("vue " + name + " render"), startTag, endTag);

      mark(startTag);
      vm._update(vnode, hydrating);
      mark(endTag);
      measure(("vue " + name + " patch"), startTag, endTag);
    };
  } else {
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }

  // 我们在观察者的构造函数中将其设置为vm._watcher,因为观察者的初始补丁可能会调用$ forceUpdate(例如,在子组件的挂载挂钩内),这依赖于已定义的vm._watcher
  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
  hydrating = false;

  // 手动挂载的实例,在自己挂载的调用挂载在其插入的挂钩中为渲染创建的子组件调用
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }

существуетnew Watcherпри звонке_renderметод, реализованныйdomрендеринга, в частности_renderЧто вы сделали, нажмите, чтобы посмотретьАнализ исходного кода Vue (перед созданием экземпляра) — инициализация глобального API (последняя глава);

После выполнения экземпляраWatcherПозже, если$nodeЕсли он не существует, это означает, что рендеринг инициализирован и выполняется.mountedкрюк;

beforeUpdate (перед обновлением данных)

Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate');
    }

};

Если текущийvueпример_isMountedдляtrueЕсли что, звоните напрямуюbeforeUpdateкрюк;

_isMounted вmountedОн устанавливается в true перед выполнением хука.

воплощать в жизньbeforeUpdateкрюк;

обновлено (после обновления данных)

function callUpdatedHooks (queue) {
  var i = queue.length;
  while (i--) {
    var watcher = queue[i];
    var vm = watcher.vm;
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated');
    }
  }
}

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

beforeDestroy (перед удалением компонента)

Vue.prototype.$destroy = function () {
    var vm = this;
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy');
};

Перед удалением проверьте, не было ли оно удалено.Если оно было удалено, непосредственноreturnвыйди;

воплощать в жизньbeforeDestroyкрюк;

уничтожен (после удаления компонента)

vm._isBeingDestroyed = true;
// 从父级那里删除自己
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
  remove(parent.$children, vm);
}
// 拆解观察者
if (vm._watcher) {
  vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
  vm._watchers[i].teardown();
}
// 从冻结对象的数据中删除引用可能没有观察者。
if (vm._data.__ob__) {
  vm._data.__ob__.vmCount--;
}
// 准备执行最后一个钩子
vm._isDestroyed = true;
// 在当前渲染的树上调用destroyed hook
vm.__patch__(vm._vnode, null);

callHook(vm, 'destroyed');

По сути, здесь стереть все следы себя;

воплощать в жизньdestroyedкрюк.

Суммировать

К этому моменту, собственно, мы уже почти поняли, что делает каждый хук жизненного цикла, поэтому такой большой объем кода может показаться не очень удобным, поэтому делаем подведение итоговlist:

  • beforeCreate: Некоторые параметры инициализируются.Если есть одинаковые параметры, параметры объединяются и выполняются.beforeCreate;
  • created: ИнициализацияInject,Provide,props,methods,data,computedа такжеwatch,воплощать в жизньcreated;
  • beforeMount: проверить наличиеelсобственность, отдать, если естьdomдействовать, исполнятьbeforeMount;
  • mounted: создать экземплярWatcher, рендерингdom,воплощать в жизньmounted;
  • beforeUpdate: в рендерингеdomпосле выполненияmountedПосле хука, когда данные обновятся, выполнитьbeforeUpdate;
  • updated: проверить текущийwatcherВ списке есть актуальные данные для обновления?watcher, если он существует, выполнитьupdated;
  • beforeDestroy: Проверить, был ли он удален, если он был удален, напрямуюreturnвыйти, иначе казнитьbeforeDestroy;
  • destroyed: Удалить все следы себя;

заключительные замечания

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

Если вы чувствуете, что есть проблема или место не очень хорошо написано, пожалуйста, прокомментируйте прямо ниже, спасибо.