# Рекомендации

Это официальное руководство по стилю для кода, специфичного для Vue. Если используете Vue в проекте, то оно поможет избежать ошибок, «велосипедов» и анти-паттернов. Однако не стоит считать, что любое подобное руководство по стилю идеально подходит для всех команд или проектов, поэтому приветствуются разумные отклонения, основанные на прошлом опыте, окружающем техническом стеке и личных ценностях.

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

Наконец, все правила разделены на четыре категории:

# Категории правил

# Приоритет A: Важно

Эти правила помогают предотвратить ошибки, поэтому изучите и соблюдайте их любой ценой. Могут быть исключения, но они должны быть очень редкими и должны выполняться только теми, у кого есть хорошие знания как JavaScript, так и Vue.

# Приоритет B: Настоятельно рекомендуется

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

# Приоритет C: Рекомендуется

Где существует несколько одинаково хороших вариантов, можно сделать собственный выбор для обеспечения консистентности. В этих правилах описываем каждый приемлемый вариант и предлагаем выбор по умолчанию. Это означает, что можно свободно сделать и другой выбор в своей кодовой базе, если придерживаетесь консистентности и имеете вескую причину. Пожалуйста, не стесняйтесь! Приспосабливаясь к стандартам сообщества будете:

  1. тренировать свой мозг, чтобы легче разбираться в большей части кода сообщества, с которым придётся столкнуться
  2. иметь возможность копировать и использовать большинство примеров кода сообщества без изменений
  3. чаще находить новых сотрудников, уже знакомых с предпочитаемым стилем кода, по крайней мере, в отношении Vue

# Приоритет D: Используйте с осторожностью

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

# Правила приоритета A: Важно (Предотвращение ошибок)

# Имена компонентов из нескольких слов важно

Имена компонентов всегда должны быть из нескольких слов, за исключением корневого компонента App и встроенных компонентов, например, <transition> или <component>.

Это предотвращает конфликты (opens new window) с существующими или будущими HTML-элементами, поскольку имена всех HTML-элементов состоят из одного слова.

Плохо

app.component('todo', {
  // ...
})
1
2
3
export default {
  name: 'Todo'
  // ...
}
1
2
3
4

Хорошо

app.component('todo-item', {
  // ...
})
1
2
3
export default {
  name: 'TodoItem'
  // ...
}
1
2
3
4

# Объявление входных параметров важно

Входные параметры должны объявляться как можно подробнее.

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

Подробное объяснение

Подробное объявление входных параметров имеет два преимущества:

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

Плохо

// Такого достаточно лишь для прототипа
props: ['status']
1
2

Хорошо

props: {
  status: String
}
1
2
3
// Гораздо лучше!
props: {
  status: {
    type: String,
    required: true,

    validator: value => {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].includes(value)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Уникальные ключи для v-for важно

Всегда используйте key с v-for.

Использование key с v-for всегда обязательно для компонентов, для поддержания внутреннего состояния компонента и его поддерева. Но и для элементов это хорошая практика для поддержания предсказуемого поведения, такого как консистентности объекта (opens new window) в анимации.

Подробное объяснение

Представим, что есть список различных todo:

data() {
  return {
    todos: [
      {
        id: 1,
        text: 'Изучить, как использовать v-for'
      },
      {
        id: 2,
        text: 'Изучить, как использовать key'
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

Проблема в том, что есть случаи, когда важно не удалять элементы, которые останутся в DOM. Например, можете использовать <transition-group> для анимации сортировки списка, или сохранении фокуса, если отображаемый элемент будет <input>. В этих случаях добавление уникального ключа для каждого элемента (например, :key="todo.id") подскажет Vue, как вести себя более предсказуемо.

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

Плохо

<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>
1
2
3
4
5

Хорошо

<ul>
  <li
    v-for="todo in todos"
    :key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>
1
2
3
4
5
6
7
8

# Избегайте использования v-if с v-for важно

Никогда не используйте v-if на том же элементе, что и v-for.

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

  • Для фильтрации элементов в списке (например, v-for="user in users" v-if="user.isActive"). В этих случаях замените users новым вычисляемым свойством, которое возвращает отфильтрованный список (например, activeUsers).

  • Чтобы избежать отображения списка, если он должен быть скрыт (например, v-for="user in users" v-if="shouldShowUsers"). В этих случаях переместите v-if выше на элемент контейнера (например, ul, ol).

Подробное объяснение

Когда Vue обрабатывает директивы, то v-if имеет более высокий приоритет чем v-for, поэтому такой шаблон:

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
6
7
8
9

Выбросит ошибку, потому что сначала будет выполняться директива v-if, а в этот момент переменной user для итерации цикла не существует.

Это можно легко исправить, выполнив итерацию по вычисляемому свойству:

computed: {
  activeUsers() {
    return this.users.filter(user => user.isActive)
  }
}
1
2
3
4
5
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
6
7
8

В качестве альтернативы, можно использовать тег <template> с v-for, чтобы обернуть элемент <li>:

<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>
1
2
3
4
5
6
7

Плохо

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
6
7
8
9

Хорошо

<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
6
7
8
<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>
1
2
3
4
5
6
7

# Локальные стили компонента важно

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

Это относится только к однофайловым компонентам. Это не требует использования атрибута scoped (opens new window). Область действия стилей можно ограничивать через CSS-модули (opens new window), стратегию на основе именования классов, такой как БЭМ (opens new window), или другой библиотекой/соглашением.

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

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

Подробное объяснение

При разработке большого проекта, работая совместно с другими разработчиками, или иногда используя сторонний HTML/CSS (например, из Auth0), консистентное ограничение области действия обеспечит применение стилей только к тем компонентам, для которых они предназначены.

Помимо атрибута scoped, использование уникальных имён классов может помочь гарантировать, что сторонний CSS не применяется к вашему собственному HTML. Например, многие проекты используют классы button, btn или icon, поэтому даже если не использовать такую стратегию, как БЭМ, добавление префикса, специфичного для конкретного приложения и/или компонента (например, ButtonClose-icon), может обеспечить некоторую защиту.

Плохо

<template>
  <button class="btn btn-close">×</button>
</template>

<style>
.btn-close {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9

Хорошо

<template>
  <button class="button button-close">×</button>
</template>

<!-- Использование атрибута `scoped` -->
<style scoped>
.button {
  border: none;
  border-radius: 2px;
}

.button-close {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
  <button :class="[$style.button, $style.buttonClose]">×</button>
</template>

<!-- Использование CSS-модулей -->
<style module>
.button {
  border: none;
  border-radius: 2px;
}

.buttonClose {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
  <button class="c-Button c-Button--close">×</button>
</template>

<!-- Использование методологии БЭМ -->
<style>
.c-Button {
  border: none;
  border-radius: 2px;
}

.c-Button--close {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Именование приватных свойств важно

Используйте область видимости модуля, чтобы приватные функции были недоступны извне. Если это невозможно, всегда используйте префикс $_ для пользовательских приватных свойств в плагине, примеси и т.д., которые не должны считаться публичным API. Затем, чтобы избежать конфликтов с кодом других авторов, также включайте именованную область (например, $_yourPluginName_).

Подробное объяснение

Vue использует префикс _ для определения собственных приватных свойств, поэтому использование того же префикса (например, _update) может привести к перезаписи свойства экземпляра. Даже если проверили и Vue в настоящее время не использует такое имя свойства, нет гарантий, что конфликт не возникнет в более поздних версиях.

Что касается префикса $, то в рамках экосистемы Vue это специальные свойства экземпляра, которые публично доступны пользователю, поэтому его использование для приватных свойств будет нецелесообразным.

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

Плохо

const myGreatMixin = {
  // ...
  methods: {
    update() {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
const myGreatMixin = {
  // ...
  methods: {
    _update() {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
const myGreatMixin = {
  // ...
  methods: {
    $update() {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
const myGreatMixin = {
  // ...
  methods: {
    $_update() {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8

Хорошо

const myGreatMixin = {
  // ...
  methods: {
    $_myGreatMixin_update() {
      // ...
    }
  }
}
1
2
3
4
5
6
7
8
// Ещё лучше!
const myGreatMixin = {
  // ...
  methods: {
    publicMethod() {
      // ...
      myPrivateFunction()
    }
  }
}

function myPrivateFunction() {
  // ...
}

export default myGreatMixin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Правила приоритета B: Настоятельно рекомендуется (Улучшение читаемости)

# Файлы компонентов настоятельно рекомендуется

Когда есть система сборки, позволяющая объединять файлы, каждый компонент должен находиться в собственном файле.

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

Плохо

app.component('TodoList', {
  // ...
})

app.component('TodoItem', {
  // ...
})
1
2
3
4
5
6
7

Хорошо

components/
|- TodoList.js
|- TodoItem.js
1
2
3
components/
|- TodoList.vue
|- TodoItem.vue
1
2
3

# Именование однофайловых компонентов настоятельно рекомендуется

Имена файлов однофайловых компонентов должны быть либо всегда в PascalCase, либо всегда в kebab-case.

PascalCase лучше всего работает с автодополнением в редакторах кода, поскольку он согласуется с тем, как ссылаемся на компоненты в JS(X) и шаблонах. Но имена файлов в смешанном регистре иногда могут создавать проблемы в файловых системах, нечувствительных к регистру, поэтому kebab-case также вполне приемлем.

Плохо

components/
|- mycomponent.vue
1
2
components/
|- myComponent.vue
1
2

Хорошо

components/
|- MyComponent.vue
1
2
components/
|- my-component.vue
1
2

# Именование базовых компонентов настоятельно рекомендуется

Базовые компоненты (также известные как презентационные, глупые или чистые компоненты) которые применяют стилистику и соглашения, специфичные для приложения, должны начинаться с определённого префикса, например Base, App или V.

Подробное объяснение

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

  • HTML-элементы,
  • другие базовые компоненты
  • сторонние UI-компоненты.

Но они никогда не содержат глобальное состояние (например, из хранилища Vuex).

Их имена часто включают имя элемента, который они оборачивают (например, BaseButton, BaseTable), если только не существует элемента для этой конкретной цели (например, BaseIcon). Если создаёте подобные компоненты для более специфического контекста, они почти всегда будут использовать эти компоненты (например, BaseButton может использоваться в ButtonSubmit).

Некоторые преимущества такого соглашения:

  • Когда базовые компоненты приложения организованы в редакторе в алфавитном порядке, они перечислены вместе, что облегчает их идентификацию.

  • Поскольку имена компонентов всегда должны быть из нескольких слов, это соглашение избавляет от необходимости выбирать произвольный префикс для простых обёрток компонентов (например, MyButton, VueButton).

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

    const requireComponent = require.context('./src', true, /Base[A-Z]\w+\.(vue|js)$/)
    requireComponent.keys().forEach(function (fileName) {
      let baseComponentConfig = requireComponent(fileName)
      baseComponentConfig = baseComponentConfig.default || baseComponentConfig
      const baseComponentName = baseComponentConfig.name || (
        fileName
          .replace(/^.+\//, '')
          .replace(/\.\w+$/, '')
      )
      app.component(baseComponentName, baseComponentConfig)
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

Плохо

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
1
2
3
4

Хорошо

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
1
2
3
4
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
1
2
3
4
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
1
2
3
4

# Именование компонентов, используемых в единственном экземпляре настоятельно рекомендуется

Компоненты, которые должны иметь только один активный экземпляр, должны именоваться с префикса The, чтобы обозначить, что он может быть только один.

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

Плохо

components/
|- Heading.vue
|- MySidebar.vue
1
2
3

Хорошо

components/
|- TheHeading.vue
|- TheSidebar.vue
1
2
3

# Именование тесно связанных компонентов настоятельно рекомендуется

Дочерние компоненты, тесно связанные со своими родителями, должны включать имя родительского компонента в качестве префикса.

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

Подробное объяснение

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

components/
|- TodoList/
   |- Item/
      |- index.vue
      |- Button.vue
   |- index.vue
1
2
3
4
5
6

или:

components/
|- TodoList/
   |- Item/
      |- Button.vue
   |- Item.vue
|- TodoList.vue
1
2
3
4
5
6

Это не рекомендуется, потому что приводит к:

  • Множеству файлов с похожими именами, что затрудняет быстрое переключение между файлами в редакторе кода.
  • Множеству подкаталогов, что увеличивает время поиска компонентов в боковой панели редактора.

Плохо

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
1
2
3
4
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
1
2
3

Хорошо

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
1
2
3
4
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
1
2
3

# Порядок слов в именах компонентов настоятельно рекомендуется

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

Подробное объяснение

Может стать интересно:

«Почему заставляем называть компоненты менее естественным языком?»

В естественном английском прилагательные и другие дескрипторы обычно располагаются перед существительными, в то время как исключения требуют соединительных слов. Например:

  • Coffee with milk
  • Soup of the day
  • Visitor to the museum

При желании можно включать эти соединительные слова в имена компонентов, но порядок всё равно важен.

Также обратите внимание, то что считается «высоким уровнем» зависит от контекста приложения. Например, представьте приложение с формой для поиска. Оно может содержать такие компоненты, как эти:

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
1
2
3
4
5
6
7

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

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue
1
2
3
4
5
6
7

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

Может возникнуть соблазн решить эту проблему по-другому, переместив все компоненты поиска в отдельный каталог «search», а потом все компоненты параметров в каталог «settings». Мы рекомендуем применять этот подход только в очень больших приложениях (например, из более 100 компонентов) по следующим причинам:

  • Переход по вложенным каталогам обычно занимает больше времени, чем прокрутка одного каталога components.
  • Конфликты имён (например, несколько компонентов ButtonDelete.vue) затрудняют быстрый переход к определённому компоненту в редакторе кода.
  • Рефакторинг становится сложнее, потому что поиска с заменой часто будет недостаточно, чтобы обновить относительные ссылки на перемещённый компонент.

Плохо

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
1
2
3
4
5
6
7

Хорошо

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
1
2
3
4
5
6
7

# Самозакрывающиеся теги компонентов настоятельно рекомендуется

Компоненты без содержимого должны быть самозакрывающимися тегами в однофайловых компонентах, строковых шаблонах и JSX — но никогда в шаблонах DOM.

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

К сожалению, HTML не разрешает пользовательским элементам быть самозакрывающимися — только официальные «void» элементы (opens new window). Поэтому данная стратегия возможна только в том случае, если компилятор шаблонов Vue может обработать шаблон до его появления в DOM, а затем предоставить DOM-совместимый HTML.

Плохо

<!-- В однофайловых компонентах, строковых шаблонах и JSX -->
<MyComponent></MyComponent>
1
2
<!-- В DOM-шаблонах -->
<my-component/>
1
2

Хорошо

<!-- В однофайловых компонентах, строковых шаблонах и JSX -->
<MyComponent/>
1
2
<!-- В DOM-шаблонах -->
<my-component></my-component>
1
2

# Стиль именования компонентов в шаблонах настоятельно рекомендуется

В большинстве проектов имена компонентов всегда должны быть в PascalCase в однофайловых компонентах и строковых шаблонах — но в kebab-case в шаблонах DOM.

PascalCase имеет следующие преимущества перед kebab-case:

  • Редакторы могут автодополнять имена компонентов в шаблонах, потому что PascalCase также используется в JavaScript.
  • <MyComponent> визуально больше отличается от элемента HTML из одного слова, нежели <my-component>, потому что различаются два символа (две заглавные буквы), а не один (дефис).
  • Если используете в шаблонах какие-либо пользовательские элементы, не относящиеся к Vue, например, веб-компонент, PascalCase гарантирует, что компоненты Vue останутся отчётливо видимыми.

К сожалению, из-за нечувствительности HTML к регистру, шаблоны в DOM должны всё равно использовать kebab-case.

Также обратите внимание, что если уже вложены значительные силы в kebab-case, консистентность с соглашениями HTML и возможность использования такого же написания во всех проектах, то это может быть важнее, чем преимущества, перечисленные выше. В таких случаях допускается использовать kebab-case везде.

Плохо

<!-- В однофайловых компонентах и строковых шаблонах -->
<mycomponent/>
1
2
<!-- В однофайловых компонентах и строковых шаблонах -->
<myComponent/>
1
2
<!-- В DOM-шаблонах -->
<MyComponent></MyComponent>
1
2

Хорошо

<!-- В однофайловых компонентах и строковых шаблонах -->
<MyComponent/>
1
2
<!-- В DOM-шаблонах -->
<my-component></my-component>
1
2

ИЛИ

<!-- Везде -->
<my-component></my-component>
1
2

# Стиль именования компонентов в JS/JSX настоятельно рекомендуется

Имена компонентов в JS/JSX всегда должны быть в PascalCase, хотя они могут быть в kebab-case внутри строк для более простых приложений, которые используют только глобальную регистрацию компонентов через app.component.

Подробное объяснение

В JavaScript PascalCase является соглашением для классов и конструкторов прототипов — по сути всё, что может иметь разные экземпляры. Компоненты Vue также имеют экземпляры, поэтому имеет смысл использовать PascalCase. Дополнительным преимуществом будет то, что использование PascalCase в JSX (и шаблонах) позволяет изучающим код легче отличать компоненты от HTML-элементов.

Однако, для приложений, которые используют только глобальные объявления компонентов через app.component, рекомендуем использовать kebab-case. Причины следующие:

  • На глобальные компоненты редко ссылаются в JavaScript, поэтому следование соглашению для JavaScript имеет меньше смысла.
  • Такие приложения всегда содержат множество шаблонов в DOM, где должен использоваться kebab-case.

Плохо

app.component('myComponent', {
  // ...
})
1
2
3
import myComponent from './MyComponent.vue'
1
export default {
  name: 'myComponent'
  // ...
}
1
2
3
4
export default {
  name: 'my-component'
  // ...
}
1
2
3
4

Хорошо

app.component('MyComponent', {
  // ...
})
1
2
3
app.component('my-component', {
  // ...
})
1
2
3
import MyComponent from './MyComponent.vue'
1
export default {
  name: 'MyComponent'
  // ...
}
1
2
3
4

# Использование полных слов при именовании компонентов настоятельно рекомендуется

В именах компонентов следует отдавать предпочтение полным словам, а не сокращениям.

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

Плохо

components/
|- SdSettings.vue
|- UProfOpts.vue
1
2
3

Хорошо

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
1
2
3

# Стиль именования входных параметров настоятельно рекомендуется

Имена входных параметров всегда должны использовать camelCase при объявлении, но kebab-case в шаблонах и JSX.

Таким образом просто придерживаемся соглашениям каждого языка. В JavaScript более естественным является camelCase. В HTML — kebab-case.

Плохо

props: {
  'greeting-text': String
}
1
2
3
<WelcomeMessage greetingText="hi"/>
1

Хорошо

props: {
  greetingText: String
}
1
2
3
<WelcomeMessage greeting-text="hi"/>
1

# Элементы с несколькими атрибутами настоятельно рекомендуется

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

В JavaScript написание объектов с несколькими свойствами в несколько строк считается хорошей практикой, потому что так легче читать. Шаблоны и JSX стоит рассматривать также.

Плохо

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
1
<MyComponent foo="a" bar="b" baz="c"/>
1

Хорошо

<img
  src="https://vuejs.org/images/logo.png"
  alt="Vue Logo"
>
1
2
3
4
<MyComponent
  foo="a"
  bar="b"
  baz="c"
/>
1
2
3
4
5

# Простые выражения в шаблонах настоятельно рекомендуется

Шаблоны компонентов должны содержать только простые выражения, а более сложные должны быть вынесены в вычисляемые свойства или методы.

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

Плохо

{{
  fullName.split(' ').map(word => {
    return word[0].toUpperCase() + word.slice(1)
  }).join(' ')
}}
1
2
3
4
5

Хорошо

<!-- В шаблоне -->
{{ normalizedFullName }}
1
2
// Комплексное выражение было вынесено в вычисляемое свойство
computed: {
  normalizedFullName() {
    return this.fullName.split(' ')
      .map(word => word[0].toUpperCase() + word.slice(1))
      .join(' ')
  }
}
1
2
3
4
5
6
7
8

# Простые вычисляемые свойства настоятельно рекомендуется

Сложные вычисляемые свойства должны быть разбиты на как можно большее количество более простых свойств.

Подробное объяснение

Более простые, хорошо названные вычисляемые свойства будет:

  • Легче тестировать

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

  • Легче читать

    Упрощение вычисляемых свойств заставляет давать каждому значению понятное имя, даже если оно не используется повторно. Это значительно облегчает другим разработчикам (и вам в будущем) задачу сосредоточиться на коде, который им нужен, и понять, что происходит.

  • Лучше приспособлены к изменяющимся требованиям

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

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

Плохо

computed: {
  price() {
    const basePrice = this.manufactureCost / (1 - this.profitMargin)
    return (
      basePrice -
      basePrice * (this.discountPercent || 0)
    )
  }
}
1
2
3
4
5
6
7
8
9

Хорошо

computed: {
  basePrice() {
    return this.manufactureCost / (1 - this.profitMargin)
  },

  discount() {
    return this.basePrice * (this.discountPercent || 0)
  },

  finalPrice() {
    return this.basePrice - this.discount
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Значения атрибутов в кавычках настоятельно рекомендуется

Непустые значения HTML-атрибутов всегда должны быть обрамлены кавычками (одинарными или двойными, в зависимости от того, что не используется в JS).

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

Плохо

<input type=text>
1
<AppSidebar :style={width:sidebarWidth+'px'}>
1

Хорошо

<input type="text">
1
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
1

# Сокращённая запись директив настоятельно рекомендуется

Сокращённую запись директив (: для v-bind:, @ для v-on: и # для v-slot) следует использовать всегда или никогда.

Плохо

<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>
1
2
3
4
<input
  v-on:input="onInput"
  @focus="onFocus"
>
1
2
3
4
<template v-slot:header>
  <h1>Здесь может быть заголовок страницы</h1>
</template>

 <template #footer>
  <p>Здесь контактная информация</p>
</template>
1
2
3
4
5
6
7

Хорошо

<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>
1
2
3
4
<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>
1
2
3
4
<input
  @input="onInput"
  @focus="onFocus"
>
1
2
3
4
<input
  v-on:input="onInput"
  v-on:focus="onFocus"
>
1
2
3
4
<template v-slot:header>
  <h1>Здесь может быть заголовок страницы</h1>
</template>

 <template v-slot:footer>
  <p>Здесь контактная информация</p>
</template>
1
2
3
4
5
6
7
<template #header>
  <h1>Здесь может быть заголовок страницы</h1>
</template>

 <template #footer>
  <p>Здесь контактная информация</p>
</template>
1
2
3
4
5
6
7

# Правила приоритета C: Рекомендуется (Минимизация произвольных выборов и накладных расходов)

# Порядок опций в компоненте/экземпляре рекомендуется

Опции компонента/экземпляра должны быть упорядочены консистентно.

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

  1. Глобальная осведомлённость (требует знаний вне компонента)

    • name
  2. Настройки компилятора шаблонов (изменяется способ компиляции шаблонов)

    • compilerOptions
  3. Зависимости шаблона (ресурсы, используемые в шаблоне)

    • components
    • directives
  4. Композиция (объединение свойств в опциях)

    • extends
    • mixins
    • provide/inject
  5. Интерфейс (интерфейс компонента)

    • inheritAttrs
    • props
    • emits
    • expose
  6. Composition API (точка входа при использовании Composition API)

    • setup
  7. Локальное состояние (локальные реактивные свойства)

    • data
    • computed
  8. События (коллбэки вызываемые реактивными событиями)

    • watch
    • События хуков жизненного цикла (в порядке их вызова)
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeUpdate
      • updated
      • activated
      • deactivated
      • beforeUnmount
      • unmounted
      • errorCaptured
      • renderTracked
      • renderTriggered
  9. Нереактивные свойства (свойства экземпляра независимые от системы реактивности)

    • methods
  10. Отрисовка (декларативное описание вывода компонента)

    • template/render

# Порядок атрибутов в элементе рекомендуется

Атрибуты в элементах (в том числе в компонентах) должны быть упорядочены консистентно.

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

  1. Определение (предоставляет параметры компонента)

    • is
  2. Отображение списка (создаёт несколько вариантов одного элемента)

    • v-for
  3. Условия (указывается отрисовывается/отображается ли элемент)

    • v-if
    • v-else-if
    • v-else
    • v-show
    • v-cloak
  4. Модификаторы отрисовки (изменяют способ отрисовки элемента)

    • v-pre
    • v-once
  5. Глобальная осведомлённость (требует знаний вне компонента)

    • id
  6. Уникальные атрибуты (атрибуты, требующие уникальных значений)

    • ref
    • key
  7. Двусторонняя привязка (объединение привязки и событий)

    • v-model
  8. Другие атрибуты (все неуказанные связанные или несвязанные атрибуты)

  9. События (обработчики событий компонента)

    • v-on
  10. Содержимое (перезаписывает содержимое элемента)

    • v-html
    • v-text

# Пустые строки между опций компонента/экземпляра рекомендуется

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

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

Хорошо

props: {
  value: {
    type: String,
    required: true
  },

  focused: {
    type: Boolean,
    default: false
  },

  label: String,
  icon: String
},

computed: {
  formattedValue() {
    // ...
  },

  inputClasses() {
    // ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Отсутствие пробелов не мешает, если компонент
// всё ещё легко читать и перемещаться по нему.
props: {
  value: {
    type: String,
    required: true
  },
  focused: {
    type: Boolean,
    default: false
  },
  label: String,
  icon: String
},
computed: {
  formattedValue() {
    // ...
  },
  inputClasses() {
    // ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Порядок секций в однофайловых компонентах рекомендуется

Однофайловые компоненты должны всегда использовать один порядок для корневых тегов секций <script>, <template> и <style>, причём <style> должен быть последним, потому что по крайней мере один из двух других всегда необходим.

Плохо

<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
1
2
3
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
1
2
3
4
5
6
7
8
9

Хорошо

<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
1
2
3
4
5
6
7
8
9
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
1
2
3
4
5
6
7
8
9

# Правила приоритета D: Использовать с осторожностью (Потенциально опасные паттерны)

# Селекторы элементов при использовании scoped используйте с осторожностью

Селекторов элементов следует избегать при использовании scoped.

Предпочитайте селекторы классов вместо селекторов элементов в стилях с атрибутом scoped, потому что большое количество селекторов элементов работает медленно.

Подробное объяснение

Для ограничения области действия стилей Vue добавляет уникальный атрибут к элементам компонента, например data-v-f3f3eg9. Затем селекторы изменяются таким образом, чтобы выбирались только элементы с этим атрибутом (например, button[data-v-f3f3eg9]).

Проблема в том, что большое количество селекторов атрибутов элементов (например, button[data-v-f3f3eg9]) будет работать значительно медленнее селекторов классов (например, .btn-close[data-v-f3f3eg9]), поэтому при любой возможности следует отдавать им предпочтение.

Плохо

<template>
  <button>×</button>
</template>

<style scoped>
button {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9

Хорошо

<template>
  <button class="btn btn-close">×</button>
</template>

<style scoped>
.btn-close {
  background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9

# Неявная коммуникация между родительским и дочерними компонентами используйте с осторожностью

Для взаимодействия между родительским и дочерним компонентом следует предпочесть входные параметры и события, вместо использования this.$parent или мутирования входных параметров.

В идеальном приложении Vue — входные параметры передаются вниз, события наверх. Придерживаясь этой концепции, компоненты будет намного проще понять. Однако, есть граничные случаи, когда мутация входных параметров или использование this.$parent могут упростить два компонента, которые уже сильно связаны между собой.

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

Плохо

app.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },

  template: '<input v-model="todo.text">'
})
1
2
3
4
5
6
7
8
9
10
app.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },

  methods: {
    removeTodo() {
      this.$parent.todos = this.$parent.todos.filter(todo => todo.id !== vm.todo.id)
    }
  },

  template: `
    <span>
      {{ todo.text }}
      <button @click="removeTodo">
        ×
      </button>
    </span>
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Хорошо

app.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },

  emits: ['input'],

  template: `
    <input
      :value="todo.text"
      @input="$emit('input', $event.target.value)"
    >
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },

  emits: ['delete'],

  template: `
    <span>
      {{ todo.text }}
      <button @click="$emit('delete')">
        ×
      </button>
    </span>
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Управление состоянием приложения без flux используйте с осторожностью

Vuex (opens new window) следует предпочесть для глобального управления состоянием, вместо использования this.$root или глобальной шины событий.

Управление состоянием через this.$root и/или использование глобальной шины событий может быть удобным для очень простых случаев, но это не подходит для большинства приложений.

Vuex — официальная flux-подобная реализация для Vue, предлагающая не только централизованное место для управления состоянием, но и инструменты для организации, отслеживания и отладки изменений состояния. Она хорошо интегрируется в экосистему Vue (включая полную поддержку Vue DevTools).

Плохо

// main.js
import { createApp } from 'vue'
import mitt from 'mitt'
const app = createApp({
  data() {
    return {
      todos: [],
      emitter: mitt()
    }
  },

  created() {
    this.emitter.on('remove-todo', this.removeTodo)
  },

  methods: {
    removeTodo(todo) {
      const todoIdToRemove = todo.id
      this.todos = this.todos.filter(todo => todo.id !== todoIdToRemove)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Хорошо

// store/modules/todos.js
export default {
  state: {
    list: []
  },

  mutations: {
    REMOVE_TODO(state, todoId) {
      state.list = state.list.filter(todo => todo.id !== todoId)
    }
  },

  actions: {
    removeTodo({ commit, state }, todo) {
      commit('REMOVE_TODO', todo.id)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- TodoItem.vue -->
<template>
  <span>
    {{ todo.text }}
    <button @click="removeTodo(todo)">
      X
    </button>
  </span>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },

  methods: mapActions(['removeTodo'])
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24