Шаблоны восходящей и нисходящей трансляции
Во Части 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