Skip to content

Правила приоритета А: Основные

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

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

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

Плохо

template
<!-- В прекомпилированных шаблонах -->
<Item />

<!-- Внутри сырого index.html -->
<item></item>

Хорошо

template
<!-- В прекомпилированных шаблонах -->
<TodoItem />

<!-- Внутри сырого index.html -->
<todo-item></todo-item>

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

При коммите кода объявление входных параметров должно быть максимально подробным - как минимум с указанием типа (типов).

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

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

  • Они документируют API компонента, что дает понимание того, как использовать компонент.
  • В режиме разработки Vue будет выводить ошибки (console.warn), если входные параметры некорректно предоставлены, помогая вам отловить потенциальные источники ошибок.

Плохо

js
// Это "окей" только при прототипировании
props: ['status']

Хорошо

js
props: {
  status: String
}
js
// Ещё лучше!
props: {
  status: {
    type: String,
    required: true,

    validator: value => {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].includes(value)
    }
  }
}

Плохо

js
// Это "окей" только при прототипировании
const props = defineProps(['status'])

Хорошо

js
const props = defineProps({
  status: String
})
js
// Ещё лучше!

const props = defineProps({
  status: {
    type: String,
    required: true,

    validator: (value) => {
      return ['syncing', 'synced', 'version-conflict', 'error'].includes(
        value
      )
    }
  }
})

Используйте :key при работе с v-for

key при работе с v-for всегда необходим на компонентах, чтобы поддерживать внутреннее состояние компонента в поддереве. Однако даже для элементов поддерживать предсказуемое поведение - хорошая практика, как например постоянство объектов в анимациях.

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

Допустим, у вас есть список дел для выполнения:

js
data() {
  return {
    todos: [
      {
        id: 1,
        text: 'Научиться использовать v-for'
      },
      {
        id: 2,
        text: 'Научиться использовать key'
      }
    ]
  }
}
js
const todos = ref([
  {
    id: 1,
    text: 'Научиться использовать v-for'
  },
  {
    id: 2,
    text: 'Научиться использовать key'
  }
])

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

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

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

Плохо

template
<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>

Хорошо

template
<ul>
  <li
    v-for="todo in todos"
    :key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

Не используйте 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, так что этот шаблон:

template
<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

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

Это должно быть исправлено при помощи итерации по вычисляемому свойству, например:

js
computed: {
  activeUsers() {
    return this.users.filter(user => user.isActive)
  }
}
js
const activeUsers = computed(() => {
  return users.filter((user) => user.isActive)
})
template
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

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

template
<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>

Плохо

template
<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

Хорошо

template
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
template
<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>

Используйте scoped-стили

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

Это релевантно только для однофайловых компонентов. Это не требует использования scoped атрибута. Скрытие стилей может быть достигнуто также при помощи CSS-модулей, методологии BEM или других библиотек/соглашений.

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

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

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

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

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

Плохо

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

<style>
.btn-close {
  background-color: red;
}
</style>

Хорошо

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

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

.button-close {
  background-color: red;
}
</style>
template
<template>
  <button :class="[$style.button, $style.buttonClose]">×</button>
</template>

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

.buttonClose {
  background-color: red;
}
</style>
template
<template>
  <button class="c-Button c-Button--close">×</button>
</template>

<!-- Использует BEM-соглашение -->
<style>
.c-Button {
  border: none;
  border-radius: 2px;
}

.c-Button--close {
  background-color: red;
}
</style>
Правила приоритета А: ОсновныеУже загружено