Привет! Я Наталья Калачева, Frontend-разработчик в AGIMA. Эта статья посвящена правилам, которые помогают упростить поддержку и расширение приложений на Vue. Тут я рассказываю, как организовать хранение компонентов, стилей и плагинов, когда использовать стор и полезные функции Vue. Первые 3 принципа я опубликовала вчера. Здесь еще 3.

4. Оборачивать библиотеки при инициализации

Лучше всего создавать отдельный файл для инициализации широко используемых плагинов. Например, вместо того чтобы тянуть Axios в компонентах непосредственно из зависимостей, вы можете создать класс с более общим именем — такой, как HTTP, чьи методы вызывают Axios под капотом. Например:

import axios from 'axios';
class $Http {
 constructor(options) {
   this.instance = axios.create({
     baseURL: options?.baseURL ?? '',
     headers: options?.headers ?? {},
     params: options?.params ?? {},
   });
 }
 get = (resource, params) => this.instance.get(resource, { params });
 // другие методы класса, интерцепторы и т.д.
}
export default $Http;

На первый взгляд, это бесполезный модуль, ведь он делает то же самое, что делал бы модуль из зависимости. Только вы вместо этого пишете импорт HTTP из «http.js». Однако это облегчает переиспользование кода и расширение общей функциональности. Мы можем расширять наш класс и добавлять новые функции/конфиги глобально.

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

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

Конечно, не все библиотеки стоит оборачивать. Если есть сомнения, можно задать себе ряд вопросов:

  • Вы не уверены в том, что реализация библиотеки на 100% удовлетворяет будущие задачи?

  • Эта зависимость требует настройки и расширения функционала?

  • Будете ли это переиспользовать в разных частях приложения?

  • Вы будете использовать только часть функционала?

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

5. Валидировать пропсы, использовать типы

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


export default {
 name: 'BlockName',
 props: {
   title: {
     type: String,
     default: ''
   }
 }
}

Если вы используете Typescript, то сложные типы можно описывать с помощью интерфейсов:

import { PropType, defineComponent } from 'vue'


interface IItem {
 id: string
 title: string
}


export default defineComponent({
 name: 'BaseList',
 props: {
   list: {
     type: Array as PropType<IItem[]>,
     default: () => []
   }
 }
})

Лучший способ ограничить пропсы определенным набором значений — использовать параметр валидатора. Это функция проверки, которая принимает значение и возвращает один из двух результатов — true или false.

export default {
 name: 'BaseIcon',
 props: {
   position: {
     type: String,
     validator: s => ['top', 'left'].includes(s)
   }
 }
};

Всё вышеперечисленное справедливо при использовании Vue 3 либо с options, либо с composition API. Разница только в setup: пропсы должны быть объявлены с помощью функции defineProps(). Например:

<script setup lang="ts">
interface IProps {
 title: string
 list: Array<IItem>
}


const { title, list } = defineProps<IProps>()
</script>

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

6. Организовать стили

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

Есть множество методологий написания CSS, например БЭМ, OOCSS, SMACSS, Atomic CSS и т. п. Среди этих подходов нет идеального — у всех свои плюсы и минусы. Поэтому можно выбрать один, а можно совместить несколько. Важно договориться с командой о едином подходе. Тогда масштабирование проекта будет даваться легче.

Нужно прописать глобальные стили, которые распространяются на всё приложение. Есть несколько стилей, которые мы хотим применить ко всем компонентам. Например, normalize.css или reset.css. Или общие стили, такие как шрифты, размеры заголовка, цвета и CSS-переменные. Добавим их в тег стиля App.vue

<template>
 <div id="app">
   <main>
     <router-view></router-view>
   </main>
 </div>
</template>


<style>
body {
 margin: 0;
}
</style>

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

<style lang="scss" scoped>
.Item {
 &__text {
   margin-bottom: 30px;
 }
}
</style>

Если стилей одного компонента много или одни и те же стили переиспользуются в нескольких компонентах, в assets я создаю отдельную папку styles/components и добавляю туда стили, которые в дальнейшем импортирую в компонент.

<style lang="scss" scoped>
@import "@/assets/styles/components/table.scss";
</style>

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

Как альтернативу можно использовать CSS Module. Они также решают проблему конфликта стилей. Но модули предполагают динамический рендеринг и отображение имен классов (например, можно настроить классы в зависимости от среды разработки или props).

<template> 
 <section>
   <div>
     <h1 :class="$style.heading">Заголовок</h1>
   </div>
 </section>
</template>




<style module>
.heading {
 font-size: 50px;
 font-weight: 900;
 text-align: center;
}
</style>


Данный модуль генерирует следующий CSS:

.ComponentName__heading__2Kxy { 
 font-size: 50px;
 font-weight: 900;
 text-align: center;
}

Заключение

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

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

Комментарии (10)


  1. gmtd
    00.00.0000 00:00
    +2

    135 просмотров и 27 "плюсиков"

    Сразу видно - хорошая статья


    1. ht-pro
      00.00.0000 00:00
      +3

      Или "плюсы" накручены ????


  1. Ilusha
    00.00.0000 00:00
    +2

    Обертку над axios нужно типизировать тоже.

    this.instance.get<MyResponseType>(resource, { params })

    В респонсе объект data будет типизирован.

    Интерфейс axios декларирован на ts. Всегда можно когда-то потом написать обертку и сделать find-replace.

    Но. Это маленькая либа. Её легко обернуть. А что с другими? ui-kit тоже оборачивать прокси-компонентами? А сверху еще одна обертка кастомизацией? Но чтобы пройти vue-tsc - нужно будет заморочиться.

    Общие ошибки в ответе 200, если такие есть, тоже лучше обрабатывать здесь же: и выкидывать кастомные Errors.

    И зачем тянуть axios или обертку над ней в компоненты? Кмк, следует описывать вызовы api отдельно, типизируя их контрактами с бэка (openapi и всё такое). upd: в первой статье вы об этом и пишите.

    P.S.: не претендую на истину, могу быть в чем-то не прав, тапками не кидать.


    1. Stonuml
      00.00.0000 00:00

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


      1. Ilusha
        00.00.0000 00:00

        Я специально разделил прокси-интерфейс и кастомизацию.

        Причем для своего ui-kit развернуть дублирующую документацию: разработчики ходят на quasar/ant.design/materialui/etc за примерами. Ведь это не просто прокся: вы предлагаете поменять стиль.

        Кастомизация - это отдельная история. Вот здесь я согласен: она нужна. Но она должна просто определять дефолтные пропсы и стилизацию.

        А расширение базовых компонентов - это уже новые компоненты :)


      1. vanxant
        00.00.0000 00:00

        Оборачивать ui компоненты это работа ради работы, оставьте это любителям реакта.

        Завтра выйдет новая модная либа компонентов для vue 4 и вы всё равно всё это выкинете не глядя.

        Просто пишите бизнес-логику максимально независимо от представления и от самого vue.


  1. SWATOPLUS
    00.00.0000 00:00
    +2

    export default $Http;

    Убейтесь об стену со своим export default, автоимпорты будут работать нигде.

    get = async (resource, params) => this.instance.get(resource, { params });

    Тут async по приколу написан?

    5. Валидировать пропсы, использовать типы

    Ну хорошо хоть где-то типы есть, еще бы export default выпилить

    Есть множество методологий написания CSS, например БЭМ, OOCSS, SMACSS, Atomic CSS и т. п.

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

    @nkalacheva

    P.S. Скудное количество материала, еще хуже чем предыдущая часть. Не пишите такой мусор.


  1. bavrr
    00.00.0000 00:00

    Стили компонента всегда изолируются при помощи директивы scoped. Это гарантирует, что любой стиль CSS
    Не гарантирует https://codesandbox.io/s/mystifying-wiles-o59j9?file=/src/main.js


    1. Vadiok
      00.00.0000 00:00

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

      Тут конечно вопрос трактовки. Но если пределы понимать как внешние элементы, то гарантирует. А так, если задать стили для текста (цвет, размер, межстрочное расстояние и прочее), то конечно же эти значения перейдут в потомков, если в потомках не определить свои стили. И единственный вариант не менять вообще ничего - использовать Shadow DOM.


  1. mr-quokka
    00.00.0000 00:00

    Добрый день, разрабатываю на Vue около пяти лет. Scoped в каких-то случаях работает, как здесь и описано, но точно не гарантирует отсутствие конфликтов в проекте, зато исправлять потом половину проекта от повторений - запомнится надолго.
    Валидацию через магические переменные (s) тоже лучше не использовать