Если вы хотите создать веб-страницу с помощью JavaScript, VueJS поможет вам и выполнит грязную работу за вас. Но есть условие: он работает только на тех блоках страницы, в которых он имеет единоличный контроль. Любая часть, которая может быть помешана другими скриптами или плагинами уже не для Vue.
Это значит, что теги head
и body
являются зонами без Vue. То есть если вы хотите управлять классом тега body
напрямую например, то у вас ничего не получится.
Так как Vue не может управлять тегами head
и body
напрямую, он все равно поможет вам управлять ими другими способами.
Почему Vue придирчив к тому, где он работает?
Vue оптимизирует рендеринг страницы с помощью виртуального DOM. Это JavaScript-представление «реального» DOM, которое Vue хранит в памяти. Обновление DOM часто бывают медленным, поэтому сначала изменения происходят в виртуальном DOM, затем он обновляет реальный DOM, так Vue собственно и оптимизирует процесс.
Эта система будет сломана, если какая-либо третья сторона внесет изменения в DOM без ведома Vue, вызывая несоответствие между реальным DOM и виртуальным DOM.
По этой причине Vue не пытается контролировать всю страницу, а только ее часть, где будет иметь единоличный контроль.
Связывание с элементом
Первое, что мы обычно делаем в проекте Vue — это связываем селектор в объекте Vue через свойство el:
1 2 3 |
new Vue({ el: '#app' }); |
Тем самым говорим Vue, с какой частью страницы мы будем работать. Vue будет работать со связанным элементом, а также со всеми дочерними элементами. Но он не может воздействовать на элементы, находящиеся вне связанного элемента, будь то одноуровневые или же родительские элементы:
1 2 3 4 5 6 7 8 9 10 11 12 |
<head> <!--Vue бессилен тут!--> </head> <body> <!--Vue бессилен здесь!--> <div id="app"> <!--Vue доминирует здесь!--> </div> <div id="not-the-app"> <!--и здесь Vue бессилен!--> </div> </body> |
Нет привязки к body
Наверное вы склонны думать, что тег body
будет лучшим местом для привязки, так как существует много причин для получения контроля над классами body
, событиями body
и т.д.
Проблема в том, что есть плагины браузеров, также сторонние скрипты, которые загрязняют body своими собственными классами, слушателями событий и даже добавляют свои дочерние элементы хотите вы этого или нет.
Это настоящий ужас для Vue, поэтому тег body находится за чертой ограничения. Фактически, начиная с версии 2, если вы попытаетесь сделать привязку к body
или head
, вы получите следующее предупреждение:
1 |
"Do not mount Vue to <html> or <body> - mount to normal elements instead." |
Управление тегами head и body
Итак, теперь, когда мы установили, что Vue должен привязываться к своему «личному» элементу внутри body, и он не может влиять на какую-либо часть DOM над этим элементом привязки, как вы сможете управлять body или head с Vue?
Ответ: вы не можете. Ну не напрямую, по крайней мере. Все, что находится вне привязанного элемента, фактически невидимо для Vue.
Но это справедливо больше для самого Vue, чем для рендеринга. Поэтому, несмотря на то, что есть элементы, находящиеся за его областью видимости, он все равно поможет вам достать их другими способами, с помощью наблюдателей и хуков жизненного цикла.
Сценарий №1: Прослушивание пользовательского ввода
Предположим, вы создаете модальное окно с Vue, и вы хотите, чтобы пользователь мог закрыть окно с помощью клавиши escape.
Vue дает вам директиву v-on для прослушивания событий, но если форма ввода находится не в фокусе во время нажатий, то ключевые события отправляются из тега body:
Поскольку body не находится в юрисдикции Vue, вы не сможете заставить Vue прослушать это событие. Вам нужно будет настроить собственный прослушиватель событий с помощью Web API:
1 2 3 4 5 6 7 8 9 10 11 12 |
var app = new Vue({ el: '#app', data: { modalOpen: false } }); document.addEventListener('keyup', function(evt) { if (evt.keyCode === 27 && app.modalOpen) { app.modalOpen = false; } }); |
Как Vue может помочь
Vue может помочь с помощью своих хуков жизненных циклов. Во-первых, используйте хук created, чтобы добавить слушатель. Это гарантирует, что данные, которые вы указываете (т. е. modalOpen), будут видны при срабатывании колбэка.
Во-вторых, используйте хук destroyed, чтобы удалить слушателя, когда он больше не нужен, для очистки памяти.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
new Vue({ el: '#app', data: { modalOpen: false }, methods: { escapeKeyListener: function(evt) { if (evt.keyCode === 27 && this.modalOpen) { this.modalOpen = false; } } }, created: function() { document.addEventListener('keyup', this.escapeKeyListener); }, destroyed: function() { document.removeEventListener('keyup', this.escapeKeyListener); }, }); |
Сценарий №2: Управление классами body
Когда пользователь открывает ваше модальное окно, вы хотите полностью деактивировать главное окно. Для этого вы можете разместить полупрозрачный блок над главным окном, чтобы окно было некликабельным, а также скрыть overflow, чтобы окно нельзя было прокручивать.
Чтобы убрать прокрутку, добавьте класс в body (назовем его modal-open), со свойством overflow: hidden.
1 2 3 |
body.modal-open { overflow: hidden; } |
Очевидно, нам нужно динамически добавлять и удалять этот класс, поскольку мы все равно хотим разрешить прокрутку, когда модальное окно закрыто. Обычно мы используем v-bind: class для выполнения этой задачи, но опять же, вы не можете привязываться к атрибутам body с помощью Vue, поэтому нам придется снова использовать Web API:
1 2 3 4 5 |
// открытие модального окна document.body.classList.add('modal-open'); // закрытие модального окна document.body.classList.remove('modal-closed'); |
Как Vue может помочь
Vue добавляет реактивные геттеры и сеттеры к каждому элементу DOM, так что, когда изменяется значение данных, он обновляет DOM. Vue позволяет писать пользовательскую логику, которая перехватывает изменения реактивных данных через наблюдатель watch.
Vue будет выполнять любые колбэки наблюдателя всякий раз, когда изменяется значение данных (в данном случае modalOpen). Мы будем использовать этот колбэк, чтобы добавить или удалить класс body:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var app = new Vue({ el: '#app', data: { modalOpen: false }, watch: { modalOpen: function(newVal) { var className = 'modal-open'; if (newVal) { document.body.classList.add(className); } else { document.body.classList.remove(className); } } } }); |
Не совсем точный перевод статьи на vuejsdevelopers.com