Руководство
Основы
- Установка
- Введение
- Экземпляр Vue
- Синтаксис шаблонов
- Вычисляемые свойства и слежение
- Работа с классами и стилями
- Условная отрисовка
- Отрисовка списков
- Обработка событий
- Работа с формами
- Основы компонентов
Продвинутые компоненты
- Регистрация компонентов
- Входные параметры
- Пользовательские события
- Слоты
- Динамические и асинхронные компоненты
- Обработка крайних случаев
Переходы и анимации
- Анимирование списков и появления/исчезновения
- Анимирование переходов между состояниями
Переиспользование и композиция
- Примеси
- Пользовательские директивы
- Render-функции и JSX
- Плагины
- Фильтры
Инструментарий
- Однофайловые компоненты
- Тестирование
- Поддержка TypeScript
- Публикация на production
Масштабирование
- Роутинг
- Управление состоянием приложения
- SSR. Отрисовка на стороне сервера
- Безопасность
Продвинутые темы
- Подробно о реактивности
Вопросы миграции
- Миграция с Vue 1.x
- Миграция с Vue Router 0.7.x
- Миграция с Vuex 0.6.x на 1.0
Мета
- Сравнение с другими фреймворками
- Присоединяйтесь к сообществу Vue.js!
- Познакомьтесь с командой
Эта документация для версий v2.x и ранее. Для v3.x, документация на русском здесь.
Обработка крайних случаев
Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.
Все возможности, описанные на этой странице, описывают способы обработки крайних случаев, что означает необычные ситуации, которые иногда требуют исключений в правилах Vue. Однако обратите внимание, что все они имеют недостатки или ситуации, когда они могут быть опасными. Они отмечены в каждом случае, поэтому помните о них, когда решаете использовать каждую из возможностей.
Доступ к элементу и компоненту
В большинстве случаев лучше избегать связи с другими экземплярами компонентов или вручную манипулировать элементами DOM. Однако есть случаи, когда это может быть уместно.
Доступ к корневому экземпляру
В каждом дочернем компоненте экземпляра new Vue
, к этому корневому экземпляру можно получить доступ через свойство $root
. Например, для этого корневого экземпляра:
|
Теперь все дочерние компоненты смогут получить доступ к этому экземпляру и использовать его в качестве глобального хранилища:
|
Это может быть удобно для демонстраций или очень маленьких приложений с несколькими компонентами. Однако этот паттерн плохо масштабируется для средних или крупных приложений, поэтому мы настоятельно рекомендуем использовать Vuex для управления состоянием в большинстве случаев.
Доступ к экземпляру родительского компонента
Подобно $root
, свойство $parent
можно использовать для доступа к родительскому экземпляру из дочернего. Это может быть заманчивым для использования в качестве ленивой альтернативы передачи данных с помощью входных параметров.
В большинстве случаев обращение к родительскому компоненту делает ваше приложение более проблематичным в отладке и поддержке, особенно если вы изменяете данные в родителе. Когда вы вернётесь к этому компоненту позднее, будет очень сложно выяснить, откуда происходит изменение данных.
Однако есть случаи, в частности библиотек общих компонентов, когда это может быть подходящим. Например, в абстрактных компонентах, которые взаимодействуют через JavaScript с API вместо отрисовки HTML, как например эти гипотетические компоненты Google Maps:
|
Компонент <google-map>
может определять свойство map
, к которому должны иметь доступ все подкомпоненты. В этом случае <google-map-markers>
может получить доступ к карте с помощью this.$parent.getMap
, чтобы добавить на карту набор маркеров. Вы можете увидеть этот шаблон в действии здесь.
Однако помните, что компоненты, построенные с использованием этого шаблона являются хрупкими. Например, представьте, что мы добавляем новый компонент <google-map-region>
и когда в нём появляется <google-map-markers>
, то он должен отображать только маркеры, попадающие в регион:
|
Затем внутри <google-map-markers>
вы можете застать себя за созданием хаков наподобие такого:
|
Это может быстро выйти из-под контроля. Поэтому, чтобы предоставлять контекстную информацию для компонентов-потомков на любую глубину вложенности, мы вместо этого рекомендуем инъекцию зависимостей.
Доступ к экземплярам дочерних компонентов и элементов
Несмотря на наличие входных параметров и событий, иногда вам может потребоваться прямой доступ к дочернему компоненту в JavaScript. Для этого вы можете назначить ссылочный ID дочернему компоненту с помощью атрибута ref
. Например:
|
Теперь в компоненте, где вы определили этот ref
, вы можете использовать:
|
для доступа к экземпляру <base-input>
. Это может быть полезно, если вы хотите например, программно добавить фокус на поле из родителя. В этом случае компонент <base-input>
может аналогичным образом использовать ref
чтобы обеспечить доступ к определённым элементам внутри него, например:
|
И даже определить методы для использования родителем:
|
Таким образом мы позволим родительскому компоненту добавлять фокус на input внутри <base-input>
с помощью:
|
Когда ref
используется вместе с v-for
, то ref будет массивом, содержащим дочерние компоненты, отображаемых от источника данных.
$refs
заполняются только после того, как компонент был отрисован, и они не реактивны. Это подразумевается только как обходной путь для прямого манипулирования потомками — вам следует избегать доступа к $refs
из шаблонов или вычисляемых свойств.
Внедрение зависимостей
Ранее, когда мы обсуждали доступ к экземпляру родительского компонента, мы показали пример:
|
В этом компоненте все потомки <google-map>
нуждались в доступе к методу getMap
, чтобы узнать с какой картой им взаимодействовать. К сожалению, использование свойства $parent
плохо масштабируется для более глубоко вложенных компонентов. Вот где внедрение зависимостей может быть полезным, используя два новых свойства экземпляра: provide
и inject
.
Опция provide
позволяет нам указать данные/методы, которые мы хотим предоставить всем компонентам-потомкам. В этом случае, это метод getMap
внутри <google-map>
:
|
Затем в любых потомках мы можем воспользоваться свойством inject
для получения специальных свойств, которые мы хотели бы добавить к этому экземпляру:
|
Вы можете увидеть полный пример здесь. Преимуществом использования в отличие от $parent
в том, что мы можем получить доступ к getMap
в любом компоненте-потомке, без раскрытия всего экземпляра <google-map>
. Это позволяет нам безопаснее продолжать разработку этого компонента, не опасаясь, что мы можем изменить/удалить что-то, на что полагается дочерний компонент. Интерфейс между этими компонентами остаётся чётко определённым, как и с props
.
Фактически вы можете думать о внедрении зависимостей как о входных параметрах «дальнего действия», за исключением случаев, когда:
- родительским компонентам не нужно знать, какие потомки используют свойства, которые он предоставляет
- компонентам-потомкам не нужно знать, откуда внедряются эти свойства
Тем не менее, у внедрения зависимостей есть недостатки. Оно связывает компоненты в вашем приложении с тем, как они в настоящее время организованы, что затрудняет рефакторинг. Свойства, указанные в provide
, также не будут реактивными. Так и было задумано, потому что использование их для создания централизованного хранилища так же плохо, как и использование $root
в тех же целях. Если свойства, которые вы хотите использовать, являются специфическими для вашего приложения, а не общего назначения, или если вы захотите обновить инъектируемые данные внутри родителей, то это хороший признак того, что вам понадобится решение для управления состоянием, например Vuex.
Подробнее об инъекции зависимостей можно прочитать на странице API.
Программное добавление прослушивателей событий
До сих пор вы видели использование $emit
и прослушивание с помощью v-on
, но экземпляры Vue предоставляют и другие методы для интерфейса событий. Мы можем:
- Прослушивать событие с помощью
$on(eventName, eventHandler)
- Прослушивать событие только один раз с помощью
$once(eventName, eventHandler)
- Прекращать прослушивание события с помощью
$off(eventName, eventHandler)
Обычно вам не придётся использовать их, но они доступны для случаев, когда вам необходимо вручную прослушивать события на экземпляре компонента. Они также могут быть полезны в качестве инструмента организации кода. Например, вы часто можете увидеть этот шаблон для интеграции сторонней библиотеки:
|
Здесь есть две потенциальных проблемы:
- Это требует сохранения
picker
в экземпляре компонента, когда, возможно, потребуется использование лишь в хуках жизненного цикла. Это не страшно, но это можно считать беспорядком. - Наш код установки хранится отдельно от нашего кода очистки, что затрудняет программную очистку всего, что мы установили.
Вы можете решить обе проблемы с помощью программного прослушивания события:
|
Используя эту стратегию, мы могли бы даже использовать Pikaday с несколькими элементами ввода, причём каждый новый экземпляр автоматически очищался бы после себя:
|
Посмотрите этот пример для полного кода. Обратите внимание, что если вам приходится делать много установок и очисток в рамках одного компонента, то лучшим решением будет, как правило, создание более модульных компонентов. В этом случае мы рекомендуем создать переиспользуемый компонент <input-datepicker>
.
Чтобы узнать больше о программных прослушивателях событий, ознакомьтесь на странице API с разделом Методы экземпляра — события.
Обратите внимание, что система событий Vue отличается от браузерного EventTarget API. Хотя они работают аналогично, $emit
, $on
, и $off
не являются псевдонимами для dispatchEvent
, addEventListener
, и removeEventListener
.
Циклические ссылки
Рекурсивные компоненты
Компоненты могут рекурсивно вызывать себя в своём собственном шаблоне. Однако, они могут делать это только с помощью опции name
:
|
Когда вы регистрируете компонент глобально с помощью Vue.component
, глобальный ID будет автоматически устанавливаться как параметр опции name
компонента.
|
Если вы не будете осторожны, рекурсивные компоненты также могут привести к бесконечным циклам:
|
Компонент, указанный выше, приведёт к ошибке «max stack size exceeded», поэтому убедитесь, что рекурсивный вызов определяется по условию (т.е. использует v-if
, который в конечном итоге будет false
).
Циклические ссылки между компонентами
Предположим, что вы создаёте дерево каталога файлов, как например в Finder или File Explorer. У вас может быть компонент tree-folder
с таким шаблоном:
|
Затем компонент tree-folder-contents
с этим шаблоном:
|
Когда вы присмотритесь, вы увидите, что эти компоненты фактически будут потомком _и_ предком в дереве отрисовки — парадокс! При регистрации компонентов глобально с помощью Vue.component
этот парадокс разрешается автоматически за вас. Если это ваш случай, можете не читать дальше.
Однако, если вы используете require/import для компонентов с помощью модульной системы, например через Webpack или Browserify, вы получите сообщение об ошибке:
|
Чтобы объяснить, что здесь происходит, давайте назовём наши компоненты A и B. Система модулей видит, что ей нужен A, но сначала A нуждается в B, но B нуждается в A, но A нуждается в B, и т. д. Она застревает в цикле, не зная, как полностью разрешить любой компонент без предварительного разрешения другого. Чтобы исправить это, нам нужно дать модульной системе точку, в которой она может сказать «A нуждается в B иногда, но нет необходимости разрешать B сначала».
В нашем случае давайте сделаем эту точку компонентом tree-folder
. Мы знаем, что потомок, создающий парадокс, является компонентом tree-folder-contents
, поэтому мы подождём, пока не будет вызван хук жизненного цикла beforeCreate
для его регистрации:
|
Или вы можете использовать асинхронный import
Webpack при локальной регистрации компонента:
|
Проблема решена!
Альтернативные определения шаблонов
Inline-шаблоны
Если у компонента-потомка присутствует специальный атрибут inline-template
, то содержимое элемента будет использовано не для распределения контента, а в качестве шаблона этого компонента. Это позволяет более гибко использовать шаблоны.
|
Все inline-шаблоны должны быть определены внутри элемента DOM, к которому присоединяется Vue.
Тем не менее, inline-template
усложняют понимание области видимости вашего шаблона. В качестве наилучшей практики рекомендуется определять шаблоны внутри компонента с помощью опции template
или в теге <template>
в файле .vue
.
X-Templates
Другой способ определения шаблонов — указывать их внутри тега script с типом text/x-template
, а затем ссылаться на шаблон по id. Например:
|
|
Все шаблоны x-template должны быть определены вне элемента DOM, к которому присоединяется Vue.
Это может быть полезным для демонстраций с большими шаблонами или в очень маленьких приложениях, но в остальных случаях их следует избегать, поскольку они отделяют шаблоны от остального определения компонента.
Контролирование обновлений
Благодаря системе реактивности Vue, она всегда знает, когда нужно выполнять обновления (если вы используете её правильно). Однако есть крайние случаи, когда вам может потребоваться принудительное обновление, несмотря на то, что никаких реактивных данных не изменилось. Также есть другие случаи, когда вы можете предотвратить ненужные обновления.
Принудительное обновление
Если вам необходимо принудительное обновление во Vue, в 99.99% случаев вы где-то совершили ошибку.
Возможно, вы не учли предостережения об обнаружении изменений в массивах или в объектах, или вы можете полагаться на состояние, которое не отслеживается системой реактивности Vue, например с помощью data
.
Однако, если вы исключили вышеизложенные варианты и оказались в крайне редкой ситуации, связанной с необходимостью принудительного обновления вручную, то вы можете сделать это с помощью $forceUpdate
.
“Дешёвые” статические компоненты с помощью v-once
Отрисовка простых элементов HTML во Vue происходит очень быстро, но иногда встречаются компоненты, в которых очень много статического контента. В таких случаях, вы можете убедиться что он будет выполнен один раз и затем закэширован, добавив директиву v-once
на корневой элемент, например:
|
Ещё раз, попробуйте не злоупотреблять этим шаблоном. Хотя это удобно в тех редких случаях, когда вам необходимо отображать много статического контента, это просто не нужно пока вы не заметите замедление при отрисовке — плюс, это может вызвать много путаницы позднее. Например, представьте себе другого разработчика, который не знаком с директивой v-once
или просто пропустил её наличие в шаблоне. Могут уйти часы, пока удастся выяснить, почему шаблон не обновляется правильно.