Руководство для начинающих по работе с компонентами во Vue

В этом руководстве мы рассмотрим работу с компонентами во Vue. Я расскажу, как создавать компоненты, как передавать данные между компонентами. А также как использовать элемент  <slot> для рендеринга дополнительного контента внутри компонента.

Как создавать компоненты во 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>

В приведенном выше примере data - это функция, которая возвращает литерал объекта. Благодаря этому каждый экземпляр компонента получает собственный объект data.

Есть несколько способов определения шаблон компонента. В предыдущем примере используется литерал объекта. Но мы также можем использовать тег <script>, встроенный в DOM шаблона.

Однофайловые компоненты

В более сложных проектах глобальные компоненты становятся громоздкими. В таких случаях лучше использовать однофайловые компонентов. Это отдельные файлы с расширением .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().

Передача данных в компоненты через свойства

Свойства позволяют передавать данные из родительского компонента в дочерний. Это дает возможность сделать компоненты более мелкими, чтобы обрабатывать определенные функции.

Предположим, что в компонент блога выводит сведения об авторе, публикации (заголовок, текст и изображения) и комментарии. Но его можно разбить на дочерние компоненты, каждый из которых отвечает за вывод определенной информации:

<BlogPost>
  <AuthorDetails></AuthorDetails>
  <PostDetails></PostDetails>
  <Comments></Comments>
</BlogPost>

При этом все данные будут переданы из родительского компонента. Он может выглядеть следующим образом:

new Vue({
  el: '#app',
  data() {
    return {
      author: {
        name: 'John Doe',
        email: 'jdoe@example.com'
      }
    }
  }
})

В упомянутом выше компоненте мы определили данные автора и информацию о посте. Далее нужно создать дочерний компонент. Назовем его 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']
})

Также можно включить валидацию при передаче свойства, чтобы убедиться, что отображаются правильные данные.  Чтобы включить валидацию в приведенном выше примере, измените компонент следующим образом:

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.

Что мы хотим сделать: объект 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 created для подключения к компоненту до его объявления, а также для настройки шины событий.

Использовать шину событий рекомендуется, если приложение не сложное.

Вложение содержимого в компоненты с использованием слотов

Если вы попробуете использовать компонент с закрывающимся тегом и поместите внутрь него содержимое, то 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, передавать данные от родительского к дочернему компоненту через свойства и шину событий. Также мы рассмотрели слоты – удобный метод для составления компонентов наиболее эффективными способами. Надеюсь, это руководство было для вас полезным.

Вадим Дворниковавтор-переводчик статьи «A Beginner’s Guide to Working With Components in Vue»