Анализ принципа Vue (2): что делал beforeCreate во время инициализации?

Vue.js

Предыдущий:Принципиальный анализ Vue (1): что такое Vue?

В предыдущей главе мы узнали, чтоnew Vue(), внутреннее исполнениеthis._init()метод, этот метод находится вinitMixin(Vue)Определяется в пределах:

export function initMixin(Vue) {
  Vue.prototype._init = function(options) {
    ...
  }
}

при исполненииnew Vue()После выполнения запускается серия инициализаций._initметод, его реализация заключается в следующем:

let uid = 0

Vue.prototype._init = function(options) {

  const vm = this
  vm._uid = uid++  // 唯一标识
  
  vm.$options = mergeOptions(  // 合并options
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
  ...
  initLifecycle(vm) // 开始一系列的初始化
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, 'created')
  ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

Сначала нужно объяснить, каждый компонент представляет собойVueПодклассы конструкторов, этот объяснит, почему это так, позже. Давайте рассмотрим его пошагово сверху вниз, сначала определим_uidСвойство, которое является уникальным частным идентификатором свойства для каждого компонента при каждой его инициализации и иногда имеет некоторые эффекты.

Вот небольшой пример его использования для поиска всех братьев и сестер компонента и отбраковки самого себя:

<div>
  ...
  <child-components />
  <child-components />  // 找到它的兄弟组件
  ... 其他组件
  <child-components />
</div>

Первый компонент для поиска должен быть определенnameсвойства, определения курсаnameАтрибуты также являются хорошей письменной привычкой. Сначала через собственный родительский компонент($parent)все подкомпоненты($children)отфильтровать то же самоеnameКомпоненты коллекции, на этот раз это один и тот же компонент, хотя иnameто же самое, но_uidразличных, и, наконец, в пределах набора в соответствии с_uidПросто избавься от себя.

Конфигурация параметров слияния

Вернитесь к основному квесту, а затем слейтеoptionsи смонтировать один на экземпляре$optionsАтрибуты. Слил что? Здесь есть два случая:

  1. Инициализировать новый Vue

в исполненииnew VueПри построении функции параметр является объектом, то есть пользовательской конфигурацией пользователя, он будет сочетаться сvueМетод прототипа, определенный ранее, глобальныйAPIсвойства; также глобальныеVue.mixinпараметры внутри, объедините их в новыйoptionsи, наконец, присвоить его новому свойству$options.

  1. Инициализация дочернего компонента

Если это инициализация дочернего компонента, в дополнение к объединению вышеперечисленного, параметры родительского компонента также будут объединены, если родительский компонент определен в дочернем компоненте.event,propsи т.п.

После объединения вы можетеthis.$options.dataдоступ к пользовательскимdataфункция,this.$options.nameДоступ к определяемому пользователем имени компонента, это комбинированное свойство важно и будет часто использоваться.

Далее будет последовательно выполняться куча методов инициализации, в первую очередь эти три:

1. initLifecycle(vm)
2. initEvents(vm)
3. initRender(vm)

1. initLifecycle(vm): Основная функция заключается в подтверждении отношения родитель-потомок компонента и инициализации некоторых свойств экземпляра.

export function initLifecycle(vm) {
  const options = vm.$options  // 之前合并的属性
  
  let 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  // 让每一个子组件的$root属性都是根组件
  
  vm.$children = []
  vm.$refs = {}
  
  vm._watcher = null
  ...
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

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

Во-первых, будет найден первый родительский компонент неабстрактного типа текущего компонента, поэтому, если текущий компонент имеет родителя, а текущий компонент не является абстрактным компонентом, он будет продолжать поиск до тех пор, пока не будет найден, и найденный родительский компонент будет присвоено свойству экземпляраvm.$parent, то текущий экземплярpushк найденному родителю$childrenСвойства экземпляра, тем самым устанавливая отношения родитель-потомок компонента. некоторые из следующих_Начало — это частный атрибут экземпляра, мы помним, что он определен здесь, а конкретное значение будет объяснено позже при его использовании.

2. initEvents(vm): Основная функция заключается в использовании родительского компонентаv-onили@Зарегистрированные пользовательские события добавляются в концентратор событий дочернего компонента.

Сначала посмотрите на место, где определен этот метод:

export function initEvents (vm) {
  vm._events = Object.create(null)  // 事件中心
  ...
  const listeners = vm.$options._parentListeners  // 经过合并options得到的
  if (listeners) {
    updateComponentListeners(vm, listeners) 
  }
}

Мы должны сначала знать, что вvueЕсть два типа событий, и они обрабатываются по-разному:

2.1родное событие

в исполненииinitEventsНа предыдущем этапе компиляции шаблона будет определено, что встречающеесяhtmlМетка или название компонента, если естьhtmlЯрлыки превратятся в настоящиеdomиспользовать послеaddEventListenerЗарегистрируйтесь для участия в собственных событиях браузера. Событие привязки mountdomПоследний этап, это просто этап инициализации, здесь в основном имеют дело с пользовательскими событиями, которые являются другим видом, вот утверждение, не обращайте внимания на неправильный порядок выполнения.

2.2пользовательское событие

происходит слияниеoptionsПосле этапа дочерние компоненты могутvm.$options._parentListenersПрочитайте пользовательское событие, переданное из родительского компонента:

<child-components @select='handleSelect' />

Формат передаваемых данных о событии:{select:function(){}}вот так, вinitEventsопределение методаvm._eventsИспользуется для хранения коллекции переданных событий.

Внутренне исполняемый методupdateComponentListeners(vm, listeners)в основном выполнятьupdateListenersметод. Этот метод имеет два тайминга выполнения, первый — это текущая фаза инициализации, а другой — финальная.patchТакже используются родные события того времени. Его роль заключается в сравнении списка новых и старых событий для определения добавления и удаления событий и обработки модификаторов событий. Теперь он в основном смотрит на добавление пользовательских событий. Его роль заключается в использовании ранее определенных$on,$emitметод для завершения передачи событий компонента родитель-потомок (подробный принцип будет объяснен в глобальномAPIунифицированное описание главы). первое использование$onпрошлоеvm.eventsСоздайте элемент коллекции массива с именем пользовательского события в центре событий. Каждый элемент массива представляет собой функцию обратного вызова, соответствующую имени события, например:

vm._events.select = [function handleSelect(){}, ...]  // 可以有多个

После завершения регистрации используйте$emitСобытие выполнения метода:

this.$emit('select')

Во-первых, он будет прочитан в центре событий$emitпервый параметр методаselectМассив коллекции объектов, а затем последовательно выполнять каждую функцию обратного вызова в массиве для завершения$emitСписок задач.

Я не знаю, заметили ли выthis.$emitЭтот метод срабатывает в текущем экземпляре компонента, поэтому принцип события может отличаться от того, что понимает большинство людей: дело не в том, что родительский компонент слушает, а дочерний компонент отправляет событие родительскому компоненту.

Вместо этого дочерний компонент отправляет события своему собственному экземпляру только потому, что функция обратного вызова определена в области действия родительского компонента, поэтому выполняется метод, определенный в родительском компоненте, что создает иллюзию связи событий между родителем и ребенок. Зная эту принципиальную характеристику, мы можем сделать еще несколькоcoolтакие вещи, как:

<div>
  <parent-component>  // $on添加事件
    <child-component-1>
      <child-component-2>
        <child-component-3 />  // $emit触发事件
      </child-component-2>
    </child-components-1>
  </parent-component>
</div>

мы можем вparent-componentвнутреннее использование$onДобавить событие в концентратор событий текущего экземпляра, находясь вchild-components-3найдено внутриparent-componentЭкземпляр компонента и вызовите соответствующее событие в его центре событий для обеспечения связи между компонентами, ответ — да! Этот принцип оказался полезным при разработке библиотек компонентов.

3. initRender(vm): Основная функция – монтироватьrenderфункционировать, чтобыvnodeМетоды.

export function initRender(vm) {
  vm._vnode = null
  ...
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)  //转化编译器的
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)  // 转化手写的
  ...
}

Основная функция – креплениеvm._cа такжеvm.$createElementДва метода, они отличаются только последним параметром, оба метода могутrenderфункционировать, чтобыvnode, вы должны увидеть разницу в названии,vm._cпреобразуется компиляторомtemplateпреобразованныйrenderфункция; иvm.$createElementПреобразование определяется пользователемrenderфункции, такие как:

new Vue({
  data: {
    msg: 'hello Vue!'
  },
  render(h) { // 这里的 h 就是vm.$createElement
    return h('span', this.msg);  
  }
}).$mount('#app');

renderаргументы функцииhто естьvm.$createElementметод для преобразования внутренне определенных данных древовидной структуры вVnodeпример.

4. callHook(vm, 'beforeCreate')

Наконец, мы хотим выполнить первый хук жизненного цикла экземпляра.beforeCreate,здесьcallHookВ чем принцип? Мы объясним в главе о жизненном цикле позже. Сейчас нам нужно только знать, что он будет выполнять определенный пользователем метод жизненного цикла.mixinСмешанные в также выполнены.

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

  • initLifecycle(vm): Подтвердить компонент (такжеvueпример) отношения родитель-потомок
  • initEvents(vm): передавать пользовательские события из родительских компонентов в дочерние.
  • initRender(vm): предоставляетrenderфункционировать, чтобыvnodeМетоды
  • beforeCreate: выполнить компонентbeforeCreateфункция ловушки

Наконец, сvueЗавершают эту главу простые вопросы для интервью:

Интервьюер улыбнулся и вежливо спросил:

  • Не могли бы вы, пожалуйстаbeforeCreateКрюкthisдоступ кdataпеременная определена в , почему и что может делать этот хук?

Дайте отпор:

  • недоступен, потому чтоvueФаза инициализации, на этот разdataПеременные в не были смонтированы вthis, на этот раз значение доступа будетundefined.beforeCreateЭтот хук редко используется в развитии бизнеса в обычное время, и он похож на внутренний плагинinstanllметод переданVue.useКогда метод установлен, он обычно выбирается вbeforeCreateВыполнить внутри этого хука,vue-routerа такжеvuexВот что он делает.

Следующий:Анализ принципа Vue (3): что вы делали перед созданием во время инициализации?

Просто нажмите «Нравится» или подпишитесь, это легко найти~

Ссылаться на:

Всесторонний углубленный анализ исходного кода Vue.js

Vue.js объясняется простым способом

Поделитесь библиотекой компонентов со всеми, вы можете ею пользоваться ~ ↓

Библиотека функциональных компонентов vue, которую вы можете использовать, и она постоянно совершенствуется...