Vue: как использовать компоненты

Обновлено 07.01.2018: Пересмотрел свежим взглядом и внёс мелкие правки. Заходите и в Telegram-канал по Vue: https://t.me/vuejs_ru


Компоненты развивают идею плагинов. Каждый из них реализует какую-то свою возможность (а если нет существующих, то можно написать и свой). Если понадобится реализовать подобное в другом месте — легко переиспользовать плагин снова. Взаимодействие можно описать простым интерфейсом: отправляем в плагин входные параметры, а для обратной связи можем отслеживать события.

Всё это справедливо и для компонентов. С тем лишь отличием, что компонент может представлять собой не только одну вещь (например, красивый и функциональный select), но и какую-то часть приложения, которая должна работать и выглядеть везде единообразно (например, форма комментирования, с аватаркой, редактором и красивым select’ом).

Иллюстрация разделения страницы на компоненты

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

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

Поэтому применение компонентного подхода теперь широко используется во многих фреймворках. Vue не остался в стороне и предоставляет прекрасные возможности по работе с компонентами.

Компонент и его структура

В разметке компоненты выглядят как нестандартные теги (например, <comment-form></comment-form>). Но компонент, в отличии от обычного тега, может “скрывать” под собой не только любую разметку, но и какую-то логику своей работы и использование других компонентов.

Описывать составляющие компонентов можно просто в коде страниц. В этом случае есть некоторые ограничения по использованию и именованию (для браузера всё должно быть валидно). Vue также имеет поддержку JSX.

Для удобной организации компонентов и их составляющих предлагается использовать vue-файлы. Такой файл состоит из трёх частей:

  • <template> для шаблона с разметкой компонента
  • <script> с логикой компонента
  • <style> со стилями компонента

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

<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
Пример файла компонента

На скриншоте примера можно подметить и другие приятные возможности vue-loader, например использование Jade (ныне Pug) и SASS, а с помощью других loader’ов для webpack можно использовать любые комбинации.

Настройка webpack для компонентов

Минимальный инструментарий для работы с vue-файлами — webpack и vue-loader. Требуется только добавить правило как обрабатывать vue-файлы в конфигурацию :

// webpack.config.js - файл конфигурации webpack
rules: [
...
{
test: /\.vue$/,
loader: 'vue-loader',
}
...
]
...

Для удобства можно начинать проект с подготовленного шаблона (его можно скопировать себе самостоятельно или устанавливать через vue-cli). Красивую подсветку кода в редакторе обеспечат плагины (Sublime Text, Atom, Vetur для VSCode).

Добавлено 18.12.2016: как правильно настраивать Webpack & vue-loader

Старт с vue-компонента

Если хочется начинать сразу с файла-компонента без дополнительной разметки и шаблонов, то нужно импортировать компонент и указать его в методе render:

import Vue from 'vue';
import App from './App.vue';
new Vue({
el: '#app',
render (h) {
return h(App)
}
});

Регистрация компонента

Так как компоненты независимы, то в какой-то момент нужно “рассказывать” одному о существовании другого.

Это можно сделать зарегистрировав компонент: глобально или локально. При первом варианте — компонент регистрируется после подключения Vue, но до создания нового экземпляра Vue:

// ... где-то раньше подключаем Vue.js
Vue.component('test-component', {
// настройки компонента
});
// ... где-то потом будет new Vue({…});

Второй вариант — локальная регистрация. В этом случае вызов компонента будет доступен только в области видимости своего родителя. В секции components перечисляем какие используем компоненты и под какими именами:

<script>
// Подключаем файл компонента
import testComponent from './test-component.vue';
// или можно так
var testComponent = require('./test-component.vue');
export default {
...
// перечисляем используемые компоненты
components: {
'test-component': testComponent,
'another-test': ... // и используем <another-test> в шаблоне
}
...
}
</script>

С ES6 можно немного сократить запись:

import testComponent from './test-component.vue';
...
components: {
testComponent
}
...

А если использовать динамические импорты webpack, то ещё короче:

components: {
testComponent: () => import('./test-component.vue')
}

И не будет собираться два столбика импортов и регистраций:

// было
import component1 from './component1.vue';
import component2 from './component2.vue';
import component3 from './component3.vue';
import component4 from './component4.vue';
...
components: {
component1,
component2,
component3,
component4,
...
}
// стало
components: {
component1: () => import('./component1.vue'),
component2: () => import('./component2.vue'),
component3: () => import('./component3.vue'),
component4: () => import('./component4.vue'),
...
}

Вызов компонента

После подключения и регистрации компонентов можно начинать использовать их в шаблоне:

<template>
...
    // Используем компонент
<test-component></test-component>
    // ... или так   
<testComponent></testComponent>
    // ... или даже так
<div is="testComponent"></div>
<component is="testComponent"></component>
    ...
</template>

Последние два варианта используют специальный атрибут is для динамического определения отображаемого компонента (см. дальше). Также он может помочь для обхода ограничений при использовании DOM в качестве шаблона компонента (в этом случае браузер парсит шаблон до Vue и может удалить весь невалидный по его мнению код).

Динамический выбор компонента

Случается что нужно показывать по условию один из нескольких компонентов. Как это сделать? Можно например воспользоваться условным рендерингом:

<div v-if="component1or2">
<component1></component1>
</div>
<div v-else>
<component2></component2>
</div>

Но чем больше вариантов выбора, тем увеличивается количество лишней разметки. Здесь и пригодится специальный атрибут is. Ему указывается строка с именем компонента. Но с помощью v-bind можно привязать к атрибуту уже результат вычисления, например вычисляемой переменной, что вернёт строку с нужным названием компонента:

// selectComponent вернёт строку с нужным названием компонента
<component v-bind:is="selectComponent"></component>
// сокращённый вариант
<component :is="selectComponent"></component>

В результате никакой лишней разметки, а выбор нужного компонента полностью вынесен в логику и может быть сколь угодно изощрённым.

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

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

Для передачи данных достаточно привязки к именованному атрибуту. Например, для передачи данных из testList под именем list:

// Передаём в компонент значение testList
// а компонент внутри должен ждать входной параметр list
<test-component v-bind:list="testList"></test-component>
// Сокращённый вариант
<test-component :list="testList"></test-component>

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

<script>
export default {
props: ['list']
...
}
</script>

Можно передавать и несколько разных данных:

// Несколько наборов данных
<test-component :list="testList" :flag="isOk"></test-component>
...
// ... в дочернем компоненте
props: ['list', 'flag']

Отслеживание событий в компоненте

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

// Отслеживание события click в дочернем компоненте
<test-component v-on:click="clickHandler"></test-component>
// сокращённая запись
<test-component @click="clickHandler"></test-component>
// ... можно отслеживать и пользовательские события
<test-component @remove="removeHandler"></test-component>
<test-component @filter="filterHandler"></test-component>

Кроме самого факта события можно передать и данные:

// ... в дочернем компоненте
// генерируем событие remove и передаем ID
this.$emit('remove', id)

В родительском компоненте останется просто добавить параметр:

// ... в родительском компоненте
methods: {
removeHandler (id) {...}
}

Надеюсь, это поможет начать работать с компонентами!