Одна из замечательных особенностей работы с Vue — это компонентный подход к созданию пользовательских интерфейсов. Это позволяет вам разбивать ваше приложение на более мелкие, многократно используемые части (компоненты), которые затем можно использовать для построения более сложной структуры.
В этом руководстве я предложу вам общее представление о работе с компонентами в Vue. Я посмотрю, как создавать компоненты, как передавать данные между компонентами (как через реквизиты, так и через шину событий) и как использовать элемент <slot>
в Vue для рендеринга дополнительного контента внутри компонента.
Каждый пример будет сопровождаться работающей демонстрацией CodePen.
Как создать компоненты в Vue
Компоненты — это, по сути, многократно используемые экземпляры Vue с именем. Существуют различные способы создания компонентов в приложении Vue. Например, в малом и среднем проекте вы можете использовать метод Vue.component для регистрации глобального компонента, например:
Vue.component('my-counter', { data() { return { count: 0 } }, template: `<div>{{ count }}</div>` }) new Vue({ el: '#app' })
Название компонента — my-counter
. Это можно использовать так:
<div id="app"> <my-counter></my-counter> </div>
При именовании вашего компонента вы можете выбрать регистр кебаба ( my-custom-component
) или регистр Pascal ( MyCustomComponent
). Вы можете использовать любой вариант, когда ссылаетесь на свой компонент из шаблона, но при ссылке на него непосредственно в DOM (как в примере выше) допустимо только имя тега case kebab.
Вы также можете заметить, что в приведенном выше примере data
— это функция, которая возвращает литерал объекта (в отличие от самого литерала объекта). Это связано с тем, что каждый экземпляр компонента получает свой собственный объект data
и не должен совместно использовать один глобальный экземпляр со всеми другими экземплярами.
Есть несколько способов определить шаблон компонента. Выше мы использовали шаблонный литерал, но мы также могли бы использовать <script tag>
помеченный text/x-template
или text/x-template
в DOM. Вы можете прочитать больше о различных способах определения шаблонов здесь .
Хотите узнать Vue.js с нуля? Эта статья является выдержкой из нашей Премиум библиотеки. Получите полную коллекцию книг Vue, охватывающих основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 9 / месяц .
Однофайловые компоненты
В более сложных проектах глобальные компоненты могут быстро стать громоздкими. В таких случаях имеет смысл разработать приложение для использования однофайловых компонентов. Как следует из названия, это отдельные файлы с расширением .vue
, которые содержат .vue
<template>
, <script>
и <style>
.
В нашем примере выше компонент App
может выглядеть так:
<template> <div id="app"> <my-counter></my-counter> </div> </template> <script> import myCounter from './components/myCounter.vue' export default { name: 'app', components: { myCounter } } </script> <style></style>
И компонент MyCounter
может выглядеть так:
<template> <div>{{ count }}</div> </template> <script> export default { name: 'my-counter', data() { return { count: 0 } } } </script> <style></style>
Как вы можете видеть, при использовании однофайловых компонентов их можно импортировать и использовать непосредственно в тех компонентах, где они необходимы.
В этом руководстве я представлю все примеры с использованием метода регистрации компонента Vue.component()
.
Использование однофайловых компонентов обычно включает в себя этап сборки (например, с помощью Vue CLI). Если вы хотите узнать больше об этом, ознакомьтесь с «Руководством для начинающих по Vue CLI» в этой серии Vue.
Передача данных в компоненты через реквизит
Реквизиты позволяют нам передавать данные из родительского компонента в дочерний компонент. Это позволяет нашим компонентам быть более мелкими, чтобы обрабатывать определенные функции. Например, если у нас есть компонент блога, мы можем отображать такую информацию, как сведения об авторе, сведения о публикации (заголовок, текст и изображения) и комментарии.
Мы можем разбить их на дочерние компоненты, чтобы каждый компонент обрабатывал определенные данные, делая дерево компонентов следующим образом:
<BlogPost> <AuthorDetails></AuthorDetails> <PostDetails></PostDetails> <Comments></Comments> </BlogPost>
Если вы все еще не уверены в преимуществах использования компонентов, найдите время, чтобы понять, насколько полезной может быть такая композиция. Если бы вы пересмотрели этот код в будущем, сразу было бы очевидно, как структурирована страница и где (то есть в каком компоненте) вы должны искать какие функции. Этот декларативный способ составления интерфейса также значительно облегчает задачу тем, кто не знаком с базой кода, быстро погрузиться в него и стать продуктивным.
Поскольку все данные будут передаваться из родительского компонента, он может выглядеть следующим образом:
new Vue({ el: '#app', data() { return { author: { name: 'John Doe', email: '[email protected]' } } } })
В вышеупомянутом компоненте мы определили детали автора и информацию о посте. Далее мы должны создать дочерний компонент. Давайте назовем дочерний компонент author-detail
. Итак, наш HTML-шаблон будет выглядеть так:
<div id="app"> <author-detail :owner="author"></author-detail> </div>
Мы передаем дочерний компонент объект- author
как реквизит с именем owner
. Здесь важно отметить разницу. В дочернем компоненте owner
— это имя объекта, с помощью которого мы получаем данные из родительского компонента. Данные, которые мы хотим получить, называются author
, который мы определили в нашем родительском компоненте.
Чтобы получить доступ к этим данным, нам нужно объявить реквизиты в компоненте author-detail
:
Vue.component('author-detail', { template: ` <div> <h2>{{ owner.name }}</h2> <p>{{ owner.email }}</p> </div> ´, props: ['owner'] })
Мы также можем включить проверку при передаче реквизита, чтобы убедиться, что передаются правильные данные. Это похоже на PropTypes в React. Чтобы включить проверку в приведенном выше примере, измените наш компонент так:
Vue.component('author-detail', { template: ` <div> <h2>{{ owner.name }}</h2> <p>{{ owner.email }}</p> </div> `, props: { owner: { type: Object, required: true } } })
Если мы передадим неправильный тип проп, в вашей консоли вы увидите ошибку, которая выглядит так, как показано ниже:
"[Vue warn]: Invalid prop: type check failed for prop 'text'. Expected Boolean, got String. (found in component <>)"
В документах Vue есть официальное руководство, которое вы можете использовать, чтобы узнать о валидации проп.
Связь от дочернего к родительскому компоненту через шину событий
События обрабатываются путем создания методов-оболочек, которые срабатывают, когда происходит выбранное событие. В качестве обновления, давайте продолжим наш оригинальный пример счетчика, чтобы он увеличивался при каждом нажатии кнопки.
Вот как должен выглядеть наш компонент:
new Vue({ el: '#app', data() { return { count: 0 } }, methods: { increment() { this.count++ } } })
И наш шаблон:
<div id="app"> {{ count }} <div> <button @click="increment">+</button> </div> </div>
Надеюсь, это достаточно просто. Как вы можете видеть, мы подключаемся к событию onClick
чтобы вызывать пользовательский метод increase
при каждом нажатии кнопки. Затем метод increase
увеличивает наше свойство данных count
. Теперь давайте расширим пример, чтобы переместить кнопку счетчика в отдельный компонент и отобразить счетчик в родительском элементе. Мы можем сделать это с помощью шины событий.
Шины событий пригодятся, если вы хотите общаться от дочернего компонента к родительскому компоненту. Это противоречит стандартному способу общения, который происходит от родителя к ребенку. Вы можете использовать шину событий, если ваше приложение недостаточно велико, чтобы требовать использования Vuex. (Подробнее об этом можно прочитать в «Начало работы с Vuex: руководство для начинающих» в этой серии Vue.)
Итак, вот что мы хотим сделать: count
будет объявлен в родительском компоненте и передан дочернему компоненту. Затем в дочернем компоненте мы хотим увеличить значение count, а также обеспечить его обновление в родительском компоненте.
Компонент приложения будет выглядеть так:
new Vue({ el: '#app', data() { return { count: 0 } }
Затем в дочернем компоненте мы хотим получить счет с помощью реквизита и иметь метод для его увеличения. Мы не хотим отображать значение count в дочернем компоненте. Мы только хотим сделать приращение от дочернего компонента и отразить это в родительском компоненте:
Vue.component('counter', { template: ` <div> <button @click="increment">+</button> </div> `, props: { value: { type: Number, required: true } }, methods: { increment() { this.count++ } } })
Тогда наш шаблон будет выглядеть так:
<div id="app"> <h3> {{ count }} </h3> <counter :count="count" /> </div>
Если вы попытаетесь увеличить значение таким образом, оно не будет работать. Чтобы это работало, мы должны отправить событие из дочернего компонента, отправить новое значение count
а также прослушать это событие в родительском компоненте.
Сначала мы создаем новый экземпляр Vue и устанавливаем его в eventBus
:
const eventBus = new Vue();
Теперь мы можем использовать шину событий в нашем компоненте. Дочерний компонент будет выглядеть так:
Vue.component('counter', { props: { count: { type: Number, required: true } }, methods: { increment() { this.count++ eventBus.$emit('count-incremented', this.count) } }, template: ` <div> <button @click="increment">+</button> </div> ` })
Событие генерируется каждый раз, когда вызывается метод increment
. Мы должны прослушать событие в главном компоненте, а затем установить для count
значение, которое мы получили через событие, которое было отправлено:
new Vue({ el: '#app', data() { return { count: 0 } }, created() { eventBus.$on('count-incremented', (count) => { this.count = count }) } })
Обратите внимание, что мы используем созданный метод жизненного цикла Vue, чтобы подключить компонент до его монтирования и настроить шину событий.
Использование шины событий хорошо, если ваше приложение не сложное, но помните, что по мере роста вашего приложения вам, возможно, придется использовать Vuex.
Вложение контента в компоненты с использованием слотов
Во всех примерах, которые мы видели до сих пор, компоненты были самозакрывающимися элементами. Однако, чтобы сделать компоненты, которые могут быть скомпонованы полезными способами, мы должны иметь возможность вкладывать их друг в друга, как мы это делаем с элементами HTML.
Если вы попробуете использовать компонент с закрывающим тегом и поместите некоторое содержимое внутрь, вы увидите, что Vue просто проглотит это. Все, что находится внутри открывающих и закрывающих тегов компонента, заменяется отображаемым выводом самого компонента:
<div id="app"> <author-detail :owner="author"> <p>This will be replaced</p> </author-detail> </div>
К счастью, слоты Vue позволяют передавать произвольное значение компоненту. Это может быть что угодно, от элементов DOM от родительского компонента до дочернего компонента. Посмотрим, как они работают.
Часть скрипта наших компонентов будет выглядеть так:
Vue.component('list', { template: '#list' }) new Vue({ el: "#app" })
Тогда шаблоны будут выглядеть так:
<div id="app"> <h2>Slots</h2> <list> <h4>I am the first slot</h4> </list> <list> <h4>I am the second slot</h4> </list> </div> <script type="text/x-template" id="list"> <div> <h3>Child Component</h3> <slot></slot> </div> </script>
Содержимое нашего компонента <list>
отображается между тегом элемента <slot></slot>
. Мы также можем использовать запасной контент для случаев, когда родитель не вводит их.
<div id="app"> <h2>Slots</h2> <list> <h4>I am the first slot</h4> </list> <list></list> </div> <script type="text/x-template" id="list"> <div> <h3>Child Component</h3> <slot>This is fallback content</slot> </div> </script>
Резервный контент будет отображаться в тех случаях, когда нет содержимого от родительского компонента.
Вывод
Это было общее введение в работу с компонентами в Vue. Мы рассмотрели, как создавать компоненты в Vue, как общаться от родителя к дочернему компоненту через реквизиты, а от дочернего к родителю — через шину событий. Затем мы завершили рассмотрение слотов, удобного метода для составления компонентов полезными способами. Надеюсь, вы нашли этот урок полезным.