Основы компонентов
Компоненты позволяют нам разделить пользовательский интерфейс на независимые и многократно используемые части и думать о каждой части отдельно. Обычно приложение организовано в виде дерева вложенных друг в друга компонентов:

Это очень похоже на то, как мы вкладываем родные HTML-элементы, но Vue реализует собственную модель компонентов, которая позволяет нам инкапсулировать пользовательский контент и логику в каждый компонент. Vue также отлично сочетается с нативными веб-компонентами. Если вам интересно, как соотносятся компоненты Vue и нативные веб-компоненты, подробнее читайте здесь.
Определение компонента
При использовании этапа сборки мы обычно определяем каждый компонент Vue в отдельном файле с расширением .vue — так называемый однофайловый компонент (сокращённо SFC):
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Ты нажал на меня {{ count }} раз.</button>
</template>Если не использовать этап сборки, компонент Vue можно определить как обычный объект JavaScript, содержащий специфические для Vue параметры:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
Ты нажал на меня {{ count }} раз
</button>`
// Также можно настроить таргетинг на шаблон в DOM:
// template: '#my-template-element'
}Шаблон вставляется сюда в виде строки JavaScript, которую Vue компилирует на лету. Вы также можете использовать селектор ID, указывающий на элемент (обычно это собственные элементы <template>) — Vue будет использовать его содержимое в качестве источника шаблона.
В приведённом выше примере определяется один компонент и экспортируется в файл .js по умолчанию, но вы можете использовать именованный экспорт для экспорта нескольких компонентов из одного и того же файла.
Использование компонента
Примечание
До конца этого руководства мы будем использовать синтаксис SFC — независимо от того, используете вы этап сборки или нет. В разделе Примеры показано использование компонентов в обоих сценариях.
Чтобы использовать дочерний компонент, нам нужно импортировать его в родительский компонент. Если мы поместили наш компонент счётчика в файл ButtonCounter.vue, то компонент будет экспортирован в файл по умолчанию:
vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Вот дочерний компонент!</h1>
<ButtonCounter />
</template>С помощью <script setup> импортированные компоненты автоматически становятся доступными для шаблона.
Также можно глобально зарегистрировать компонент, сделав его доступным для всех компонентов данного приложения без необходимости импортировать его. Плюсы и минусы глобальной регистрации рассматривается в специальном разделе Регистрация компонентов.
Компоненты можно использовать сколько угодно раз:
template
<h1>Здесь много дочерних компонентов!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />Обратите внимание, что при нажатии на кнопки каждая из них ведет свой собственный, отдельный count. Это происходит потому, что каждый раз, когда вы используете компонент, создается его новый экземпляр.
В SFC рекомендуется использовать имена тегов в регистре PascalCase для дочерних компонентов, чтобы отличить их от собственных HTML-элементов. Хотя в родном HTML имена тегов не чувствительны к регистру, Vue SFC — это скомпилированный формат, поэтому мы можем использовать в нем имена тегов, чувствительные к регистру. Мы также можем использовать /> для закрытия тега.
Если вы создаете свои шаблоны непосредственно в DOM (например, как содержимое собственного элемента <template>), шаблон будет подчиняться собственному поведению браузера при разборе HTML. В таких случаях необходимо использовать kebab-case и явные закрывающие теги для компонентов:
template
<!-- если этот шаблон записан в DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>Подробнее см. в разделе Предостережения по разбору шаблонов в DOM.
Передача пропсов
Если мы создаем блог, то нам, скорее всего, понадобится компонент, представляющий запись в блоге. Мы хотим, чтобы все посты в блоге имели одинаковое визуальное оформление, но разное содержание. Такой компонент не будет полезен, если вы не сможете передать ему данные, такие как название и содержание конкретного поста, который мы хотим отобразить. Вот тут-то и пригодятся пропсы.
Пропсы — это пользовательские атрибуты, которые вы можете зарегистрировать для компонента. Чтобы передать заголовок компоненту BlogPost, мы должны объявить его в списке принимаемых компонентом пропсов, используя макрос defineProps:
vue
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>defineProps — это макрос компилятора, который доступен только внутри <script setup> и не нуждается в явном импорте. Объявленные пропсы автоматически отображаются в шаблоне. defineProps также возвращает объект, содержащий все переданные компоненту пропсы, чтобы при необходимости мы могли получить к ним доступ в JavaScript:
js
const props = defineProps(['title'])
console.log(props.title)Смотрите также: Типизация пропсов компонента
Если вы не используете <script setup>, пропсы должны быть объявлены с помощью свойства props, и объект props будет передан в setup() в качестве первого аргумента:
js
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}Компонент может иметь сколько угодно пропсов, и по умолчанию любому параметру может быть передано любое значение.
После регистрации пропа вы можете передавать ему данные в качестве пользовательского атрибута, например, так:
template
<BlogPost title="Мое путешествие с Vue" />
<BlogPost title="Ведение блога с помощью Vue" />
<BlogPost title="Почему Vue так интересен" />Однако в обычном приложении у вас, скорее всего, будет массив постов в родительском компоненте:
js
const posts = ref([
{ id: 1, title: 'Мое путешествие с Vue' },
{ id: 2, title: 'Ведение блога с помощью Vue' },
{ id: 3, title: 'Почему Vue так интересен' }
])Затем нужно вывести компонент для каждого из них, используя v-for:
template
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>Обратите внимание, как синтаксис v-bind (:title="post.title") используется для передачи динамических значений пропсов. Это особенно полезно, когда вы не знаете заранее, какой именно контент вы собираетесь отображать.
На данный момент это всё, что вам нужно знать о пропсах, но если вы дочитали эту страницу до конца и чувствуете себя комфортно, рекомендуем вернуться позже, чтобы прочитать полное руководство по пропсам.
Прослушивание событий
По мере развития нашего компонента <BlogPost> некоторые функции могут потребовать обратной связи с родительским компонентом. Например, мы можем решить включить функцию доступности, чтобы увеличить текст записей в блоге, оставив при этом размер остальной части страницы по умолчанию.
В родителе мы можем поддержать эту возможность, добавив свойство postFontSize через ref:
js
const posts = ref([
/* ... */
])
const postFontSize = ref(1)Это можно использовать в шаблоне для управления размером шрифта всех записей блога:
template
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>Теперь давайте добавим кнопку в шаблон компонента <BlogPost>.:
vue
<!-- пропускаем <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Увеличить текст</button>
</div>
</template>Кнопка ещё ничего не делает — мы хотим, чтобы нажатие на нее сообщило родителю, что он должен увеличить текст всех сообщений. Для решения этой проблемы в компонентах предусмотрена собственная система событий. Родитель может выбрать прослушивание любого события на экземпляре дочернего компонента с помощью v-on или @, точно так же, как и в случае с собственным событием DOM:
template
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>Затем дочерний компонент может вызвать событие на себя, вызвав встроенный метод $emit, передав имя события:
vue
<!-- пропускаем <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Увеличить текст</button>
</div>
</template>Благодаря слушателю @enlarge-text="postFontSize += 0.1" родитель получит событие и обновит значение postFontSize.
Мы можем опционально объявить испускаемые события с помощью макроса defineEmits:
vue
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>Здесь документируются и опционально проверяются все события, которые испускает компонент. Это также позволяет Vue избежать неявного применения их в качестве нативных слушателей к корневому элементу дочернего компонента.
Как и defineProps, defineEmits используется только в <script setup> и не нуждается в импорте. Он возвращает функцию emit, которая эквивалентна методу $emit. Его можно использовать для эмиссии событий в секции <script setup> компонента, где $emit недоступен напрямую:
vue
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>Смотрите также: Типизация событий компонента
Если вы не используете <script setup>, вы можете объявить испускаемые события с помощью опции emits. Вы можете получить доступ к функции emit как к свойству контекста установки (передается в setup() в качестве второго аргумента):
js
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}На данный момент это всё, что вам нужно знать о событиях пользовательских компонентов, но если вы закончили читать эту страницу и чувствуете себя комфортно, рекомендуем вернуться позже, чтобы прочитать полное руководство по Пользовательским событиям.
Распространение контента с помощью слотов
Как и в случае с HTML-элементами, часто бывает полезно передать компоненту содержимое, например, так:
template
<AlertBox>
Случилось что-то плохое.
</AlertBox>Что может выглядеть примерно так:
Это ошибка для демонстрационных целей
Случилось что-то плохое.
Этого можно добиться с помощью пользовательского элемента Vue <slot>:
vue
<template>
<div class="alert-box">
<strong>Это ошибка для демонстрационных целей</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>Как вы видите выше, мы используем <slot> как место, куда мы хотим поместить содержимое — и это всё. Мы закончили!
На данный момент это всё, что вам нужно знать о слотах, но если вы закончили читать эту страницу и чувствуете себя комфортно, рекомендуем вернуться позже, чтобы прочитать полное руководство по Слотам.
Динамические компоненты
Иногда полезно динамически переключаться между компонентами, например, в интерфейсе с вкладками:
Вышеописанное стало возможным благодаря элементу Vue <component> со специальным атрибутом is:
template
<!-- Компонент изменяется при изменении CurrentTab -->
<component :is="tabs[currentTab]"></component>В приведённом выше примере значение, переданное в :is, может содержать либо:
- строку с именем зарегистрированного компонента, ИЛИ
- фактический импортированный объект компонента
Вы также можете использовать атрибут is для создания обычных HTML-элементов.
При переключении между несколькими компонентами с помощью <component :is="..."> каждый компонент будет размонтирован при переключении на следующий. Мы можем заставить неактивные компоненты оставаться «живыми» с помощью встроенного компонента <KeepAlive>.
Предостережения по разбору шаблонов в DOM
Если вы пишете свои шаблоны Vue непосредственно в DOM, Vue придется получить строку шаблона из DOM. Это приводит к некоторым оговоркам, связанным с собственным поведением браузеров при разборе HTML.
Примечание
Следует отметить, что ограничения, рассмотренные ниже, применимы только в том случае, если вы пишете свои шаблоны непосредственно в DOM. Они НЕ применяются, если вы используете шаблоны строк из следующих источников:
- Однофайловые компоненты
- Вложенные строки шаблонов (например,
template: '...') <script type="text/x-template">
Нечувствительность к регистру
HTML-теги и имена атрибутов нечувствительны к регистру, поэтому браузеры интерпретируют любые символы верхнего регистра как строчные. Это означает, что когда вы используете шаблоны в DOM, имена компонентов PascalCase и имена свойств в верблюжьем регистре или имена событий v-on должны использовать их эквиваленты в кебабном регистре (с разделителями-дефисами):
js
// camelCase (верблюжий регистр) в JavaScript
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}template
<!-- kebab-case в HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>Самозакрывающиеся теги
В предыдущих примерах кода мы уже использовали самозакрывающиеся теги для компонентов:
template
<MyComponent />Это связано с тем, что анализатор шаблонов Vue рассматривает /> как указание на завершение любого тега, независимо от его типа.
Однако в шаблонах внутри DOM мы всегда должны включать явные закрывающие теги:
template
<my-component></my-component>Это связано с тем, что спецификация HTML позволяет опускать закрывающие теги только для нескольких определённых элементов, наиболее распространёнными из которых являются <input> и <img>. Для всех остальных элементов, если вы опустите закрывающий тег, родной HTML-парсер будет считать, что вы не завершили открывающий тег. Например, следующий фрагмент:
template
<my-component /> <!-- мы намерены закрыть тег здесь... -->
<span>привет</span>будет разобран как:
template
<my-component>
<span>привет</span>
</my-component> <!-- но браузер закроет его здесь. -->Ограничения на размещение элементов
Некоторые элементы HTML, такие как <ul>, <ol>, <table> и <select>, имеют ограничения на то, какие элементы могут появляться внутри них, а некоторые элементы, такие как <li>, <tr> и <option> могут появляться только внутри некоторых других элементов.
Это приведет к проблемам при использовании компонентов с элементами, имеющими такие ограничения. Например:
template
<table>
<blog-post-row></blog-post-row>
</table>Пользовательский компонент <blog-post-row> будет поднят как недопустимый контент, что приведет к ошибкам в конечном отображаемом выводе. Мы можем использовать специальный атрибут is в качестве обходного пути:
template
<table>
<tr is="vue:blog-post-row"></tr>
</table>Примечание
При использовании в собственных HTML-элементах значение is должно иметь префикс vue:, чтобы интерпретироваться как компонент Vue. Это необходимо, чтобы избежать путаницы с нативными настраиваемыми встроенными элементами.
Это всё, что вам нужно знать о предостережениях по разбору шаблонов в DOM на данный момент — и, собственно, завершение Основ Vue. Поздравляем! Нам предстоит ещё многому научиться, но для начала мы рекомендуем сделать перерыв и поиграть с Vue самостоятельно — построить что-нибудь интересное или посмотреть некоторые из примеров, если вы этого ещё не сделали.
Как только вы почувствуете себя комфортно после полученных знаний, переходите к изучению руководства, чтобы узнать больше о компонентах.