Перейти к содержанию

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

Примечание

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

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

Файлы компонентов

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

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

Плохо

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

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

Хорошо

components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue

Регистр имени однофайлового компонента

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

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

Плохо

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

Хорошо

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

Имена базовых компонентов

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

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

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

  • элементы HTML,
  • другие базовые компоненты, и
  • компоненты пользовательского интерфейса сторонних разработчиков.

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

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

Некоторые преимущества этой конвенции:

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

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

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

    js
    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)
    })

Плохо

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

Хорошо

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

Тесно связанные имена компонентов

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

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

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

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

components/
|- TodoList/
   |- Item/
      |- index.vue
      |- Button.vue
   |- index.vue

или:

components/
|- TodoList/
   |- Item/
      |- Button.vue
   |- Item.vue
|- TodoList.vue

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

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

Плохо

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

Хорошо

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

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

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

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

Возможно, вам интересно:

"Зачем заставлять имена компонентов использовать менее естественный язык?"

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

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

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

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

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

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

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue

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

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

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

Плохо

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

Хорошо

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

Самозакрывающиеся компоненты

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

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

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

Плохо

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

Хорошо

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

Регистр имени компонента в шаблонах

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

У PascalCase есть несколько преимуществ перед kebab-case:

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

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

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

Плохо

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

Хорошо

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

ИЛИ

template
<!-- Everywhere -->
<my-component></my-component>

Регистр имени компонента в 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.

Плохо

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

Хорошо

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

Полные имена компонентов

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

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

Плохо

components/
|- SdSettings.vue
|- UProfOpts.vue

Хорошо

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

Регистр имени параметра

В именах параметров при объявлении всегда должен использоваться «верблюжий» регистр (camelCase). При использовании внутри шаблонов в DOM параметр должен быть в «шашлычном» регистре (kebab-case). Шаблоны однофайловых компонентов и JSX могут использовать параметры как в «шашлычном», так и в «верблюжьем» регистрах. Регистр должен быть однообразным — если вы решили использовать параметр в «верблюжьем» регистре, убедитесь, что не используете «шашлычную» нотацию в своем приложении.

Плохо

js
props: {
  'greeting-text': String
}
js
const props = defineProps({
  'greeting-text': String
})
template
// для DOM-шаблонов
<welcome-message greetingText="hi"></welcome-message>

Хорошо

js
props: {
  greetingText: String
}
js
const props = defineProps({
  greetingText: String
})
template
// для SFC — пожалуйста, убедитесь, что ваш регистр соответствует проекту
// Можно использовать любой из вариантов, но мы не рекомендуем смешивать два разных стиля регистра
<WelcomeMessage greeting-text="hi"/>
// или
<WelcomeMessage greetingText="hi"/>
template
// для DOM-шаблонов
<welcome-message greeting-text="hi"></welcome-message>

Многоатрибутные элементы

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

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

Плохо

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

Хорошо

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

Простые выражения в шаблонах

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

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

Плохо

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

Хорошо

template
<!-- В шаблоне -->
{{ normalizedFullName }}
js
// Сложное выражение было перенесено в вычисляемое свойство
computed: {
  normalizedFullName() {
    return this.fullName.split(' ')
      .map(word => word[0].toUpperCase() + word.slice(1))
      .join(' ')
  }
}
js
// Сложное выражение было перенесено в вычисляемое свойство
const normalizedFullName = computed(() =>
  fullName.value
    .split(' ')
    .map((word) => word[0].toUpperCase() + word.slice(1))
    .join(' ')
)

Простые вычисляемые свойства

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

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

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

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

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

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

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

  • Более высокая адаптивность к изменяющимся требованиям

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

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

Плохо

js
computed: {
  price() {
    const basePrice = this.manufactureCost / (1 - this.profitMargin)
    return (
      basePrice -
      basePrice * (this.discountPercent || 0)
    )
  }
}
js
const price = computed(() => {
  const basePrice = manufactureCost.value / (1 - profitMargin.value)
  return basePrice - basePrice * (discountPercent.value || 0)
})

Хорошо

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

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

  finalPrice() {
    return this.basePrice - this.discount
  }
}
js
const basePrice = computed(
  () => manufactureCost.value / (1 - profitMargin.value)
)

const discount = computed(
  () => basePrice.value * (discountPercent.value || 0)
)

const finalPrice = computed(() => basePrice.value - discount.value)

Значения атрибутов в кавычках

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

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

Плохо

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

Хорошо

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

Сокращения директив

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

Плохо

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

<template #footer>
  <p>Вот контактная информация</p>
</template>

Хорошо

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

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

<template #footer>
  <p>Вот контактная информация</p>
</template>
Правила приоритета B: Настоятельно рекомендуется