vue2_rus

Основы компонентов

Базовый пример

Вот пример компонента Vue:

// Определяем новый компонент, названный button-counter
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">Счётчик кликов — {{ count }}</button>'
})

Компоненты — это переиспользуемые экземпляры Vue со своим именем. В примере выше это <button-counter>. Его можно использовать как пользовательский тег внутри корневого экземпляра Vue, созданного с помощью new Vue:

<div id="components-demo">
  <button-counter></button-counter>
</div>

Так как компоненты это переиспользуемые экземпляры Vue, то они принимают те же настройки что и new Vue, такие как data, computed, watch, methods, перехватчики жизненного цикла. Единственными исключениями будут несколько специфичных для корневого экземпляра настройек, например el.

Переиспользование компонентов

Компоненты можно переиспользовать столько раз, сколько захотите:

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

Обратите внимание, при нажатиях на кнопки, каждая изменяет свой собственный, отдельный count. Это потому, что каждый раз когда вы используете компонент будет создан его новый экземпляр.

Свойство data должно быть функцией

При определении компонента <button-counter> вы могли заметить, что data не была представлена в виде объекта, например так:

data: {
  count: 0
}

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

data: function () {
  return {
    count: 0
  }
}

Если бы не было этого правила, нажатие на одну кнопку повлияло бы на данные всех других экземпляров, как например тут:

Организация компонентов

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

Component Tree

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

Чтобы использовать эти компоненты в шаблонах, они должны быть зарегистрированы, чтобы Vue узнал о них. Есть два типа регистрации компонентов: глобальная и местная. До сих пор мы регистрировали компоненты только глобально, используя Vue.component:

Vue.component('my-component-name', {
  // ... настройки ...
})

Компоненты, зарегистрированные глобально, могут использоваться в шаблоне любого корневого экземпляра Vue (new Vue) созданного впоследствии — и даже внутри всех компонентов, расположенных в дереве компонентов этого экземпляра Vue.

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

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

Ранее мы создавали компонент для записи блога. Проблема в том, что этот компонент бесполезен, если не будет возможности передавать ему данные, такие как заголовок и содержимое записи журнала, которую мы хотим показать. Вот для чего нужны входные параметры.

Входные параметры — это пользовательские атрибуты, которые вы можете установить на компоненте. Когда значение передаётся в атрибут входного параметра, оно становится свойством экземпляра компонента. Чтобы передать заголовок в компонент нашей записи журнала, мы можем включить его в список входных параметров, которые принимает компонент, с помощью настройки props:

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

Компонент может принимать столько входных параметров, сколько захотите, и по умолчанию любое значение может быть передано в любой входной параметр. В шаблоне выше вы увидите, что мы можем получить доступ к этому значению в экземпляре компонента, как и к любому свойству data.

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

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

Однако, в типичном приложении у вас наверняка будет массив записей в data:

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

Тогда нужно отобразить компонент для каждой записи:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
></blog-post>

Как мы видим выше, можно использовать v-bind для динамической передачи данных во входные параметры. Это особенно полезно, когда вы не знаете заранее точного содержимого, которое потребуется отобразить, например после загрузки записей журнала из API.

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

Один корневой элемент

При создании компонента <blog-post>, ваш шаблон в конечном итоге будет содержать не только название:

<h3>{{ title }}</h3>

По крайней мере, вы захотите отобразить и содержимое записи в журнал:

<h3>{{ title }}</h3>
<div v-html="content"></div>

Однако, если вы попробуете сделать это в шаблоне, Vue покажет ошибку с пояснением что каждый компонент должен иметь один корневой элемент. Вы можете исправить эту ошибку, обернув шаблон в родительский элемент, например так:

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

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

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
  v-bind:content="post.content"
  v-bind:publishedAt="post.publishedAt"
  v-bind:comments="post.comments"
></blog-post>

Это подходящее время для перестроения компонента <blog-post>, чтобы вместо длинного списка принимать лишь один входной параметр post:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <div v-html="post.content"></div>
    </div>
  `
})

Приведённый выше пример и некоторые далее используют шаблонные строки JavaScript, чтобы сделать многострочные шаблоны более читаемыми. Эта возможность не поддерживается Internet Explorer (IE), поэтому если вам нужна поддержка IE и нет возможности использовать преобразование (например с помощью Babel или TypeScript), то используйте запись с обратными слэшами для многострочных шаблонов вместо них.

Теперь, когда добавляется новое свойство в объект post, оно будет автоматически доступно внутри <blog-post>.

Прослушивание событий из дочерних компонентов в родительских компонентах

По мере разработки нашего компонента <blog-post> для некоторых возможностей может потребоваться передавать данные обратно в родительский компонент. Например, мы можем решить включить функцию для доступности, чтобы увеличивать размер текста в записях журнала, оставив остальную часть страницы с размером текста по умолчанию.

В родительском компоненте мы можем добавить свойство postFontSize для этой возможности:

new Vue({
  el: '#blog-posts-events-demo',
  data: {
    posts: [/* ... */],
    postFontSize: 1
  }
})

Которое будет использоваться в шаблоне для управления размером шрифта для всех записей журнала:

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
  </div>
</div>

Теперь давайте добавим кнопку для увеличения текста прямо перед содержимым каждой записи журнала:

Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>
        Увеличить размер текста
      </button>
      <div v-html="post.content"></div>
    </div>
  `
})

Проблема в том, что эта кнопка ничего не делает:

<button>
  Увеличить размер текста
</button>

Когда мы нажимаем на кнопку, нужно сообщить родительскому компоненту, что ему необходимо увеличить размер текста для всех записей журнала. К счастью, экземпляры Vue предоставляют собственную систему событий для решения этой проблемы. Родительский компонент может прослушивать любые события на экземпляре дочернего компонента с помощью v-on, как мы это делаем с родными событиями DOM:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

Затем дочерний компонент может сгенерировать событие с помощью встроенного метода $emit, передавая ему имя события:

<button v-on:click="$emit('enlarge-text')">
  Увеличить размер текста
</button>

Благодаря прослушиванию события v-on:enlarge-text="postFontSize += 0.1", родительский компонент отследит событие и обновит значение postFontSize.

Передача данных вместе с событием

Иногда бывает полезно отправить определённые данные вместе с событием. Например, если захотим, чтобы компонент <blog-post> отвечал за то, насколько нужно увеличивать текст. В таком случае, мы можем использовать второй параметр $emit для предоставления этого значения:

<button v-on:click="$emit('enlarge-text', 0.1)">
  Увеличить размер текста
</button>

Затем, когда мы прослушиваем событие в родителе, мы можем получить доступ к данным, переданным с событием, через $event:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

Или, если обработчик события будет методом:

<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>

Тогда значение будет передано первым аргументом:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

Использование v-model на компонентах

Пользовательские события также могут использоваться для создания нестандартных элементов ввода, которые работают через v-model. Помните, что:

<input v-model="searchText">

делает то же самое, что и:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

При использовании на компоненте, v-model вместо этого делает следующее:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

Чтобы это действительно работало, элемент <input> внутри компонента должен:

Вот это в действии:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

Теперь v-model будет прекрасно работать с этим компонентом:

<custom-input v-model="searchText"></custom-input>

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

Распределение содержимого слотами

Как и с обычными HTML-элементами, часто бывает полезным передать компоненту содержимое, например:

<alert-box>
  Произошло что-то плохое.
</alert-box>

Что может выглядеть примерно так:

Произошло что-то плохое.

К счастью, эта задача легко решается с помощью пользовательского элемента <slot> у Vue:

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Ошибка!</strong>
      <slot></slot>
    </div>
  `
})

Как вы видите выше, мы просто добавляем слот там, куда хотим подставлять содержимое — и это всё. Готово!

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

Динамическое переключение компонентов

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

Показанное выше стало возможным с помощью элемента Vue <component> со специальным атрибутом is:

<!-- Компонент меняется при изменении currentTabComponent -->
<component v-bind:is="currentTabComponent"></component>

В примере выше currentTabComponent может содержать:

Посмотрите этот пример чтобы поэкспериментировать с полным кодом, или эту версию для примера привязки к объекту с настройками компонента вместо указания его имени.

Обратите внимание, атрибут может использоваться и с обычными HTML-элементами, однако они будут рассматриваться как компоненты, а это значит, что все атрибуты будут привязываться как DOM-атрибуты. Для того чтобы некоторые свойства, такие как value работали так, как вы ожидаете, следует привязывать их с использованием изменителя .prop.

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

Особенности разбора DOM-шаблона

Некоторые HTML-элементы, такие как <ul>, <ol>, <table> и <select> имеют ограничения на то, какие элементы могут отображаться внутри них, или например элементы, такие как <li>, <tr> и <option> могут появляться только внутри других определённых элементов.

Это приведёт к проблемам при использовании компонентов с элементами, которые имеют такие ограничения. Например:

<table>
  <blog-post-row></blog-post-row>
</table>

Пользовательский компонент <blog-post-row> будет поднят выше, так как считается недопустимым содержимым, вызывая ошибки в итоговой отрисовке. К счастью, специальный атрибут is предоставляет решение этой проблемы:

<table>
  <tr is="blog-post-row"></tr>
</table>

Следует отметить, что этого ограничения не будет если вы используете строковые шаблоны из одного из следующих источников:

На данный момент это всё, что вам нужно знать об особенностях разбора DOM-шаблонов — и на самом деле это окончание раздела Основы документации Vue. Наши поздравления! Ещё есть чему поучиться, но мы рекомендуем сначала отвлечься и попробовать поработать с Vue, самостоятельно построить что-нибудь интересное.

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