Этот документ предлагает для вас ряд правил по разработке компонентов Vue которые:
- потом будет легче для вашей команды (или для вас в будущем) понять или найти что и как работает
- ваш редактор кода (IDE, среда разработки) поймет с меньшим количеством ошибок и предложит лучшие подсказки
- улучшит переиспользование в данном проекте и в других ваших проектах
- лучше кешируются и выделяются в отдельные компоненты
- Модульная разработка
- Наименование компонентов Vue
- Выражения в компонентах должны быть простыми
- Оставляйте свойства простыми
- Правильно используйте свойства компонента
- Определяйте
this
какcomponent
- Структура компонента
- Именование событий
- Избегайте
this.$parent
- Используйте
this.$refs
осторожно - Используйте ограниченные стили
- Документируйте API компонента
- Добавляйте демо
- Форматируйте код файлов
Всегда старайтесь чтобы ваше приложение состояло из небольших модулей, каждый из которых умеет выполнять только одну функцию, но делает это хорошо.
Модуль по определению это небольшая ограниченная часть приложения. "Строительный блок", самодостаточный функционально. Организация Vue позволяет создавать подобные модули ориентируясь на визуальные компоненты.
Модули небольших размеров легче взять для использования - понять что они делают, дорабатывать или переиспользовать. И вам и всей вашей команде.
Старайтесь чтобы каждый Vue компонент соответствовал принципам FIRST:
- решающий одну задачу,
- независимый,
- переиспользуемый,
- небольшой,
- простой в тестировании.
Имя каждого компонента должно соответствовать следующим критериям:
- Понятное: в меру детальным, в меру абстрактным
- Короткое: не более 2-3 слов
- Произносимое: чтобы его можно было упомянуть в обсуждении
- Имя компонента используется людьми и должно облегчать коммуникацию
<!-- правильно -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>
<!-- неправильно -->
<btn-group></btn-group> <!-- короткое, но произносить - язык сломаешь -->
<ui-slider></ui-slider> <!-- все компоненты - так или иначе UI элементы, приставка не нужна -->
<slider></slider> <!--не соответствует спецификации HTML5 -->
Вы можете использовать инлайн-выражения в шаблонах Vue - это самые обычные Javascript выражения. Они дают максимальную свободу и мощность, однако из-за этого они могут стать слишком сложными. Не злоупотребляйте этим - оставляйте инлайн-выражения простыми.
- Сложные выражения сложнее прочесть и понять.
- Инлайн-выражения нельзя переиспользовать, это очевидно, ведет дублированию кода и ухудшению его качества.
- Редакторы и IDE обычно не могут парсить такие выражения, а значит у вас не будет автодополнения и валидации.
Простое правило - если код Javascript инлайн-выражения становится слишком сложным - выносите его как отдельный метод в блок methods или computed-свойство, соответственно в блок computed.
<!-- правильно -->
<template>
<h1>
{{ `${year}-${month}` }}
</h1>
</template>
<script type="text/javascript">
export default {
computed: {
month() {
return this.twoDigits((new Date()).getUTCMonth() + 1);
},
year() {
return (new Date()).getUTCFullYear();
}
},
methods: {
twoDigits(num) {
return ('0' + num).slice(-2);
}
},
};
</script>
<!-- неправильно -->
<template>
<h1>
{{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
</h1>
</template>
Хотя Vue и поддерживает передачу атрибутов в виде сложных объектов, старайтесь избегать этого. Старайтесь ограничиться простыми типами JavaScript и функциями для этого. Не передавайте сложные объекты в компоненты-наследники.
- Используя для каждого свойства отдельный атрибут - API вашего компонента будет более наглядным.
- Такой подход совместим с API к которому мы все привыкли у нативных HTML(5) элементов.
- Созданые вами атрибуты будет легче понять другим членам команды.
- При передаче сложных объектов сразу не видно, какие из его свойств далее используются, - это затруднит рефакторинг.
Используйте отдельные атрибуты для каждой опции и передавайте в нее примитив (флаг, строку, число) или функцию.
<!-- правильно -->
<range-slider
:values="[10, 20]"
min="0"
max="100"
step="5"
:on-slide="updateInputs"
:on-end="updateResults">
</range-slider>
<!-- неправильно -->
<range-slider :config="complexConfigObject"></range-slider>
В Vue свойства компонента (props
) являются отражением его API. Ясное и понятное API делает ваши компоненты более простыми для использования другими разработчиками.
Свойства передаются с использованием специальных атрибутов тега. Эти атрибуты могут быть либо указаны как пустые значения (:attr
), либо присвоены строкам (:attr="value"
или v-bind:attr="value"
). Обратите внимание на подобные возможности при описании свойств.
Грамотное использование свойств гарантирует, что компонент всегда будет отрабатывать без ошибок. Даже если в последствии ваши компоненты будут использоваться не так как вы предполагали изначально.
- Используйте свойства по умолчанию для указания значений свойств.
- Используйте свойство
type
для валидации значений свойства. - Всегда проверяйте что свойство определено прежде чем его использовать.
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
export default {
props: {
max: {
type: Number, // это обеспечивает проверку, что свойство max будет типа Number
default() { return 10; },
},
min: {
type: Number,
default() { return 0; },
},
value: {
type: Number,
default() { return 4; },
},
},
};
</script>
В контексте кода компонента Vue this
всегда означает экземпляр самого компонента. Таким образом если вам понадобится обратиться к ней в другом контексте сделайте так, чтобы this
означало component
.
То есть, не используйте устаревшие конструкции присваивания вроде const self = this;
. Можно и нужно использовать component
в Vue компонентах для этого.
- Присваивая
this
к переменной названнойcomponent
напрямую укажет тем кто это будет использовать, что это означает сам компонент.
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
console.log(this.hello());
}
}
};
</script>
<!-- неправильно -->
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
const self = this; // не нужно
console.log(self.hello());
},
},
};
</script>
Добейтесь, чтобы описание компонента было понятным и логичным.
- Экспортируемый объект (речь о
.js
файле или блоке<script>
в.vue
файле) компонента, построенный в каждом случае по одинаковым принципам, ускорит работу с ним других разработчиков и будет служить внутренним стандартом. - Если свойства будут расположены по алфавиту их будет легче просмотреть и найти нужное.
- Группировка сходных свойств также облегчает чтение и ориентирование в коде, например props, data, computed; далее watch, и methods; далее lifecycle methods, и тд.
- Используйте имя компонента
name
. Далее разработка с использованием vue devtools будет более удобной с именованными компонентами. - Используйте одну из отраслевых технологий для именования CSS элементов, например BEM.
- Предпочтительный порядок расположения блоков в
.vue
файле: template, потом script и далее style. Потому что большую часть времени любой разработчик проводит в написании HTML и далее JavaScript.
Структура компонента, описание свойств в логичном порядке:
<template lang="html">
<div class="Ranger__Wrapper">
<!-- ... -->
</div>
</template>
<script type="text/javascript">
export default {
// обязательно не забываем имя к.
name: 'RangeSlider',
// можем использовать композицию уже существующих к.
extends: {},
// перечисление свойств и переменных
props: {
bar: {}, // еще лучше если по-алфавиту
foo: {},
fooBar: {},
},
data() {},
computed: {},
// когда внутри используются другие к.
components: {},
// методы
watch: {},
methods: {},
// методы жизненного цикла к.
beforeCreate() {},
mounted() {},
};
</script>
<style scoped>
.Ranger__Wrapper { /* ... */ }
</style>
В Vue все инлайн-выражения и методы компонента напрямую относятся к VM (ViewModel) и меняют ее состояние. При декларации собственных событий важно их грамотно называть, чтобы избежать сложности при дальнейшей разработке и использовании компонента.
- Разработчики могут использовать совпадающие имена, что может вызвать проблемы.
- Полная свобода в выборе имён событий может также привести к проблемам с обработкой шаблонов.
- В названиях событий стоит использовать кебаб нотацию
kebab-cased
. - Название компонента должно быть уникальным и отражать что в нем происходит, например:
upload-success
,upload-error
илиdropzone-upload-success
,dropzone-upload-error
- В именах компонентов лучше использовать только существительные и глаголы, например:
client-api-load
,drive-upload-success
(источник)
Vue поддерживает вложенности компонентов, поэтому дочерние компоненты могут обращаться к данным родителя.
Обращение к внутреннему состоянию компонента снаружи нарушает принцип FIRST. Старайтесь избегать конструкции this.$parent
. Возможны случаи, когда это разумный выход, но это слишком плохая практика, чтобы использовать его всегда.
- Компонент Vue, как и любой другой, должен работать изолированно. Если для работы требуется взаимодействия с соседними скоупами, то нарушается принцип компонентной разработки.
- Если компоненту требуется обращение к соседям - такой компонент не может быть полноценно переиспользован.
- Передавайте данные из родителя в дочерний компонент используя атрибуты и свойства.
- Передавайте методы используя коллбеки и выражениях в атрибутах.
- В обратную сторону: дочерние компоненты должны генерировать события, которые будет перехватывать родитель.
Vue как и React поддерживает обращение к другим компонентам и html-элементам с использованием атрибута ref
.
Через обращение к this.$refs
разработчик может получить доступ к контексту других компонентов или тегов. В большинстве случаев можно не использовать this.$refs
для обращения к другим компонентам.
- Если компонент не работает в изоляции это признак плохого дизайна.
- Для подавляющего большинства случаев, достаточно использовать свойства компонента и его события.
- Серьезно относитесь к дизайну API ваших компонентов.
- Старайтесь избегать умножений и ветвлений пути исполнения кода в компонентах. Наличие таких фрагментов является признаком того, что API не достаточно общее, либо вам нужно создать и использовать другие компоненты для других юзкейсов.
- Используя компонент, обратите внимание на свойства: если какого-то из них не хватает, то добавьте их сами и/или создайте тикет, если это osc библиотека.
- Тоже самое с событиями - если чего-то не хватает, значит другой разработчик (или вы сами, в прошлом) не добавил их. Для исправления добавьте отсутствующее или проверьте бизнес-логику компонента, возможно, это событие уже не используется, тогда его можно просто удалить.
- Используйте
this.$refs
, только если других путей нет и вам никак не обойтись событиями и свойствами. - Если по-другому никак, то отдавайте предпочтение
refs
, а не jQuery илиdocument.queryElement
. Так вы останетесь на одном уровне абстракции (который даёт Vue) и не будете мешать их в трудно понимаемую кучу.
<!-- отлично, можно обойтись без ref -->
<range :max="max"
:min="min"
@current-value="currentValue"
:step="1"></range>
<!-- тут придется использовать this.$refs -->
<modal ref="basicModal">
<h4>Basic Modal</h4>
<button class="primary" @click="$refs.basicModal.close()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>
<!-- Modal component -->
<template>
<div v-show="active">
<!-- ... -->
</div>
</template>
<script>
export default {
// ...
data() {
return {
active: false,
};
},
methods: {
open() {
this.active = true;
},
hide() {
this.active = false;
},
},
// ...
};
</script>
<!-- тут можно было обойтись событиями -->
<template>
<range :max="max"
:min="min"
ref="range"
:step="1"></range>
</template>
<script>
export default {
// ...
methods: {
getRangeCurrentValue() {
return this.$refs.range.currentValue;
},
},
// ...
};
</script>
В Vue тег компонента является кастомным HTML-тегом, который может использоваться как корневой элемент CSS стилей (vertical-slider .list-element
). Или имя компонента может быть использовано как префикс CSS класса всех стилей компонента (.vertical-slider .list-element
).
- Ограничивать CSS стили областью компонента делает поведение UI более предсказуемым, а также предохраняет переопределение стилей других элементов на странице из-за сходства селекторов (т.н. "утечку стилей").
- Использование одинакового имени для названия папки модуля, названия компонента и корневого CSS стиля, делает прозрачным для разработчиков, что это части одного целого.
Используйте имя компонента как неймспейс префикс используя методологии BEM и OOCSS и важно не пропускайте атрибут scoped
на теге <style>
.
Использование этого атрибута оставит инструкцию для компилятора Vue добавить дополнительную сигнатуру на каждый класс, присутствующий в стилях вашего компонента. Это позволит при парсинге браузером (если он поддерживает такую возможность) применять ваши стили к тегам, которые составляют ваш компонент по всему приложению и только к ним, предотвращая т.н. "утечку стилей".
<style scoped>
/* правильно */
.MyExample { }
.MyExample li { }
.MyExample__item { }
/* неправильно */
.My-Example { } /* не ограничен именем компонента или модуля, не соответствует спецификации BEM */
</style>
Экземпляр Vue компонента создается при размещении элемента компонента в коде приложения. Дополнительная конфигурация экземпляра осуществляется при использовании атрибутов. Для того, чтобы компонент мог быть успешно переиспользован другими разработчиками, эти атрибуты и есть API вашего компонента. Они могут быть доходчиво описаны в сопроводительном файле README.md
.
- Документация предлагает разработчику высокоуровневое описание компонента без необходимости вникать в его код. Это делает использование компонента более быстрым и простым.
- API компонента в Vue это набор его атрибутов. Именно с их помощью он может быть дополнительно настроен. То есть именно атрибуты компонента являются важными для тех разработчиков, которые будут его в дальнейшем использовать.
- Документация формализует API, показывает разработчикам какая часть функционала сохраняется для обратной совместимости при модификации кода компонента.
- Название
README.md
это по факту отраслевой стандарт названия для документации, которую разработчику стоит прочесть прежде чем использовать проект. Многие платформы управления кодом (Github, Bitbucket, Gitlab) по умолчанию показывают содержание README-файла при просмотре контента любой директории.
Добавляйте README.md
файл в папку с файлами одного компонента:
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
В этом README файле можно описать функционал модуля и варианты использования. Для компонентов Vue самым полезным является описать атрибуты, т.к. именно они являются выражением API компонента.
Добавьте index.html
файл с демо вида компонента в разных конфигурациях показывая как компонент может быть использован.
- Наличие демо показывает, что модуль работает отдельно.
- Демо помогает другим разработчикам посмотреть на результаты - что получится прежде чем разбираться с кодом и/или документацией.
- Демо компонента исчерпывающе показывает различные варианты конфигурации его использования.
Линтеры улучшают качество кода и это помогает отлавливать синтаксические ошибки. .vue
файлы можно обрабатывать плагином eslint-plugin-html
. Если вы используете vue-cli
то ESLint является доступной опцией по умолчанию.
- Использование линтера гарантирует, что все разработчики читают и пишут код с одинаковыми правилами форматирования.
- Использование линтера помогает обнаружить и избежать ошибок до того как код файла сохранен.
Чтобы линтеры могли выделять Javascript код из ваших vue
файлов, он должен быть внутри тега <script></script>
. Также старайтесь сохранять инлайн-выражения простыми (см. выше) так как линтер не может их распарсить.
Настройте линтер, указав ему, что определена глобальная переменная vue
в секции opts
.
ESLint для использования требуется плагин ESLint HTML чтобы извлечь (распарсить) Javascript из .vue
файлов компонентов.
Настройки ESLint сохраняем в файле modules/.eslintrc
(так чтобы редактор кода также мог его интерпретировать):
{
"extends": "eslint:recommended",
"plugins": ["html"],
"env": {
"browser": true
},
"globals": {
"opts": true,
"vue": true
}
}
Запуск ESLint
eslint modules/**/*.vue
JSHint может парсить HTML (используя --extra-ext
) и выделять код в script (используя --extract=auto
).
Настройки JSHint сохраняем в файле modules/.jshintrc
(так чтобы редактор кода также мог его интерпретировать):
{
"browser": true,
"predef": ["opts", "vue"]
}
Запуск JSHint
jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/
Внимание: JSHint не работает с .vue
, только .html
.
Сделайте форк этого репозитория и далее предлагайте PR. Или просто создайте тикет.
Mikhail Kuznetcov
- Github shershen08
- Twitter @legkoletat