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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Имена компонентов должны всегда состоять из нескольких слов, за исключением корневого компонента App и встроенных компонентов самого Vue, например, <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

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

Непустые значения 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. Модификаторы шаблона (изменяется способ компиляции шаблонов)
    • delimiters
  3. Зависимости шаблона (ресурсы, используемые в шаблоне)
    • components
    • directives
  4. Композиция (объединение свойств в опциях)
    • extends
    • mixins
    • provide/inject
  5. Интерфейс (интерфейс компонента)
    • inheritAttrs
    • props
    • emits
  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]).

Проблема в том, что большое количество селекторов атрибутов элементов (opens new window) (например, button[data-v-f3f3eg9]) будет значительно медленнее селекторов классов (opens new window) (например, .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
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

Хорошо

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

Хорошо

// 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

Deployed on Netlify.
Последнее обновление: 2021-03-17, 19:22:15 UTC