Шаблоны восходящей и нисходящей трансляции

Во Части 2 этой серии мы использовали шаблоны $parent и $children, чтобы компоненты могли взаимодействовать в трехуровневой иерархии. Ближе к концу у нас возник вопрос для еще более сложной иерархии компонентов, например. что, если есть десять уровней компонентов? Нужно ли нам делать что-то вроде:

this.$parent.$parent.$parent.$parent.$emit('anyEvent', args);

Не говоря уже о дочерних компонентах, которые трудно отследить.

Это приводит к шаблону, который будет представлен здесь, в части 3.

Во-первых, все примеры компонентов, с которыми мы работали, образуют структуру данных, называемую двоичным деревом. Ниже приведен рисунок из документации Vue для вложенных компонентов в память компьютера во время работы приложения Vue:

Вот еще одно бинарное дерево с большим количеством уровней:

Теперь предположим, что у нас есть следующие компоненты, вложенные вместе:

В App мы привязываем событие с именем change:font к Parent, функция обратного вызова handleChangeFont обновляет размер шрифта для Parent и его потомков в соответствии с переданным размером аргумента.

Parent такой же, как и в предыдущих примерах, он поддерживает список desserts и передает его ChildA:

ChildA и GrandchildA просты, ChildA получает реквизит и передает его GrandchildA:

А вот так вид выглядит на данный момент:

Теперь для некоторых бизнес-требований пользователи хотят изменить размер шрифта, нажав кнопку с именем Change Font Size в GrandchildA:

Мы можем сделать это, используя шаблон $parent из Части 2:

Строка 30 — это шаблон «$parent» в действии. Нажмите на кнопку, размер шрифта в представлении станет 20px:

Потрясающий!

Но, как видите, со сложностью вложенности нам пришлось бы кодировать что-то вроде:

this.$parent.$parent.$parent.$parent.$parent.$emit('change:font', '20px');

если пользователи хотят добавить эту кнопку в компонент grand-grand-grandchild.

Пригодилось бы лучшее решение в большой древовидной структуре.

Схема восходящего вещания

Если компонент хочет проинформировать компонент-предок, инициировав одно из своих событий, этот компонент может буквально «транслировать» всем своим предкам о событии, которое он хочет инициировать, и если в одном компоненте-предке зарегистрировано это конкретное событие, он выполнит функция обратного вызова автоматически. Мы можем реализовать этот механизм событий на объекте-прототипе Vue, чтобы все экземпляры VueComponent могли иметь к нему доступ через this.

В main.js давайте создадим функцию с именем $upwardBroadcast для Vue.prototype:

Функция $upwardBroadcast имеет два параметра:

  • event: событие транслируется вверх от текущего компонента
  • args: данные передаются при генерировании события

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

Во-первых, в строке 12 мы сохраняем родителя текущего компонента. В строках 12–16, если родитель существует, он будет использовать экземпляр родителя для генерации события, затем перейти к родителю родителя, родителю родителя родителя и т. д. Цикл while останавливается, когда родителя больше нет, что означает, что он достиг верхнего (корневого) узла дерева.

Теперь давайте посмотрим, как использовать его для улучшения предыдущего шаблона «$parent» в GrandchildA. Очень просто, всего одна строка изменения:

Строка 31 заменяет строку 30 и использует функцию $upwardBroadcast через this, транслирует событие change:font и передает аргумент '20px'. Если мы нажмем кнопку, размер шрифта изменится, как и раньше:

Специальная записка

Здесь я говорю «использует функцию $upwardBroadcast через this», а не «в» this, потому что $upwardBroadcast определено не в экземпляре VueComponent, созданном из функции-конструктора VueComponent, а в прототип конструктора Vue — как мы делали в main.js. Да, для лучшего понимания Vue.js требуется прочная основа основ JavaScript, вот почему мне так нравится Vue — вы не просто используете фреймворк для выполнения работы, но и укрепляете и углубляете базовые знания JavaScript.

Но если вы немного подумаете об этом, то как получается, что экземпляр VueComponent может получить доступ к прототипу конструктора Vue? На самом деле, Vue сделал одну вещь поверх цепочки прототипов JavaScript — он модифицировал точки VueComponent.prototype.

Кроме того, имя функции начинается со знака $, и это только потому, что это соглашение для всех встроенных свойств и методов в Vue.js.

Схема нисходящего вещания

Теперь давайте реализуем механизм нисходящей трансляции. В main.js давайте создадим еще одну функцию с именем $downwardBroadcast на Vue.prototype:

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

Во-первых, мы получаем всех потомков текущего компонента, и для каждого потомка он будет генерировать событие. Здесь то, что отличается от одного дочернего элемента, имеющего только одного родителя в $upwardBroadcast, заключается в том, что теперь у каждого дочернего элемента может быть много дочерних элементов, поэтому, если есть какие-либо дочерние компоненты текущего дочернего элемента, нам нужно повторить ту же логику, как показано в строке 28.

Это идеальный случай для рекурсии, и давайте реализуем его:

В теле функции мы создаем еще одну функцию с именем downwardBroadcast. Сначала мы выполняем эту функцию, передавая массив this.$children текущего компонента, как показано в строке 33. Затем в пределах downwardBroadcast мы проходим по массиву дочерних элементов, и если в текущем дочернем элементе есть дочерние элементы, мы снова выполняем downwardBroadcast, передавая $children текущего ребенка.

Теперь наш main.js выглядит так:

Пришло время увидеть его в действии. Мы будем транслировать событие с именем show:year в App вниз всем его потомкам после нажатия новой кнопки с именем Display current year, а переданным аргументом будет текущий год:

В ChildA мы привязываем это событие к GrandchildA, функция обратного вызова ChildA.showYear():

Нажмите на кнопку, появится окно предупреждения:

Трансляция мощная, не так ли?

Инкапсулировать функции (хуки/стиль композиции)

Одна вещь, которую мы можем улучшить, — это переместить функции из main.js в отдельный файл — src/hooks/events.js, чтобы этот файл содержал функции, улучшающие систему событий в Vue.prototype:

Следуя соглашению об именах Hooks или Composition API, мы создаем две новые функции с именами useUpwardBroadcast и useDownwardBroadcast, параметром является функция-конструктор Vue. В теле каждой функции определена предыдущая функция.

Сейчас в main.js:

мы можем импортировать эти две функции и запускать их для улучшения Vue.prototype, если нам это нужно.

В следующей части этой серии мы рассмотрим еще один мощный шаблон компонентов Vue.js.

Вот все статьи из этой серии:

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 1

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 2

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 3

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 4

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 5

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 6

Шаблоны взаимодействия компонентов Vue.js (без Vuex) — часть 7