company_banner

4 рекомендации по разработке крупномасштабных проектов на Vue

Автор оригинала: Chameera Dulanga
  • Перевод
Как известно, Vue.js — это фреймворк, основанный на JavaScript, популярность которого в последние годы стремительно растёт.

У роста популярности Vue есть множество причин. Сюда входят простота фреймворка, лёгкость его интеграции в существующие проекты, удобство работы с ним, не слишком сильные ограничения, накладываемые на Vue-приложения. Всё это, кроме того, помогает Vue конкурировать с Angular и React. На самом деле, возникает такое ощущение, что Vue, во многих отношениях, соответствует уровню других популярных фреймворков и библиотек.

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



В этом материале я хочу представить вашему вниманию 4 развёрнутых рекомендации по разработке крупномасштабных Vue-проектов.

1. Используйте слоты Vue для того чтобы сделать код понятнее


При настройке взаимоотношений компонентов чаще всего используют модель «родитель-ребёнок». Но в некоторых ситуациях такая модель может оказаться далеко не самой удачной. Представьте себе, что у вас имеется один родительский компонент, в котором размещено большое количество дочерних компонентов. Это означает, что вам, для налаживания взаимодействия компонентов, придётся использовать большое количество входных параметров (props) и генерировать множество событий. В таких условиях в коде очень быстро заведётся беспорядок. Именно с такими ситуациями сталкиваются разработчики крупномасштабных проектов. Но во Vue есть механизмы, предназначенные именно для решения подобных проблем.

Речь идёт о слотах. Они применяются для организации альтернативного способа представления взаимоотношений типа «родитель-ребёнок». Слоты, а именно — элементы <slot>, дают разработчику возможность по-новому организовывать код. Вот простой пример использования слота:

<div class="demo-content">
   <slot></slot>
</div>

Когда такой компонент будет рендериться, в нём тег <slot></slot> будет заменён содержимым тега <demo-content>:

<demo-content>
  <h2><font color="#3AC1EF">Hi!</font></h2>
  <class-name name="Welcome to Vue!"></class-name>
</demo-content>

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

2. Создавайте независимые компоненты и многократно используйте их


При разработке компонентов следуйте принципу FIRST, делая компоненты узконаправленными (Focused), независимыми (Independent), пригодными для многократного использования (Reusable), маленькими (Small) и тестируемыми (Testable).

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

Для создания компонентов и для управления ими можно пользоваться специализированными системами наподобие Bit. Вот материал об этом.

3. Поддерживайте хранилище Vuex в хорошо организованном состоянии


Vuex — это паттерн управления состоянием Vue-приложений и соответствующая библиотека. С помощью Vuex можно организовать централизованное хранилище данных для всех компонентов приложения. Мне доводилось видеть комментарии о Vuex, авторы которых говорят, что Vuex ограничивает разработчиков в возможностях структурирования проекта и не даёт им делать то, что им нужно. Я с подобными высказываниями не согласен. Vuex, на самом деле, помогает разработчикам, которые следуют набору определённых принципов, структурировать проекты, делая их лучше организованными.

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

  • Состояния (states): используются для хранения данных приложения.
  • Геттеры (getters): применяются для организации доступа к объектам состояния за пределами хранилища.
  • Мутации (mutations): нужны для модификации объектов состояния.
  • Действия (actions): используются для применения мутаций.

Исходя из предположения о том, что вы знакомы с этими компонентами, давайте поговорим о вышеупомянутых принципах:

  1. Состоянием уровня приложения нужно управлять централизованно, размещая его в хранилище.
  2. Изменения в состояние всегда должны вноситься с использованием мутаций.
  3. Асинхронная логика должна быть инкапсулирована, она должна воздействовать на состояние только посредством действий.

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

├── index.html
├── main.js
├── api
├── components
└── store
    ├── index.js          
    ├── actions.js
    ├── mutations.js
    └── modules

▍Модульная организация хранилища Vuex


В этом материале мы говорим о крупномасштабных проектах. От таких проектов можно ожидать того, что используемые в них файлы будут очень большими и сложными. Программистам, создающим такие проекты, нужны механизмы управления хранилищем, отражающие их нужды. При этом структура хранилища не должна быть неоправданно усложнённой. Поэтому рекомендуется организовывать хранилища Vuex по модульному принципу, настраивая их в соответствии со своим видением наилучшей структуры таких хранилищ. Не существует определённого способа разбиения хранилищ на модули. Некоторые разработчики применяют подход, основанный на возможностях проекта, а некоторые — основанный на моделях данных. Итоговое решение о разделении хранилища на модули принимает программист, ответственный за проект. Разумно структурированное хранилище оказывает положительное влияние на удобство работы над проектом в долгосрочной перспективе.

Вот пример модульного подхода к структурированию хранилища:

store/
   ├── index.js 
   └── modules/
       ├── module1.store.js
       ├── module2.store.js
       ├── module3.store.js
       ├── module4.store.js
       └── module5.store.js

▍Использование вспомогательных методов


Выше я говорил о 4 компонентах, используемых в хранилищах Vuex. Рассмотрим ситуацию, когда нужно обращаться к состояниям и работать с геттерами, когда необходимо вызывать в компонентах действия и мутации. В подобных случаях не нужно создавать множество методов или вычисляемых свойств. Здесь можно просто воспользоваться вспомогательным методами (mapState, mapGetters, mapMutations и mapActions), применение которых позволит избежать неоправданного увеличения объёма кода. Поговорим об использовании этих вспомогательных методов.

▍mapState


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

import { mapState } from 'vuex'
export default {
  computed: mapState({
    count: state => state.count,
    countAlias: 'count',
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

▍mapGetters


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

import { mapGetters } from 'vuex'
export default {
  computed: {
    ...mapGetters([
      'count1',
      'getter1',
    ])
  }
}

▍mapMutations


Вспомогательный метод mapMutations используется в компонентах для применения мутаций. Он задаёт соответствие между методами компонента и вызовами store.commit. Кроме того, этот метод можно использовать для передачи в хранилище каких-то данных.

import { mapMutations } from 'vuex'
export default {
  methods: {
    ...mapMutations({
      cal: 'calculate' // отображает `this.cal()` на `this.$store.commit('calculate')`
    })
  }
}

▍mapActions


Вспомогательный метод mapActions применяется в компонентах для диспетчеризации действий. Он задаёт соответствие между методами компонента и вызовами store.dispatch.

import { mapActions } from 'vuex'
export default {
  methods: {
    ...mapActions({
      cal: 'calculate' // отображает `this.cal()` на `this.$store.dispatch('calculate')`
    })
  }
}

4. Не забудьте о модульных тестах


Тестирование — это ещё один важный аспект любого проекта. Разработчики должны тестировать то, что они создают, независимо от важности и размеров проектов. Это особенно актуально в крупномасштабных проектах, в которых имеются сотни или даже тысячи маленьких функций. Наша задача — протестировать каждую из них. Решить эту задачу помогают модульные тесты. Они позволяют программисту тестировать отдельные модули кода. Такие тесты не только позволяют выявлять и устранять ошибки. Они ещё и повышают уровень уверенности программиста или команды программистов в правильности своих действий тогда, когда в существующий код вносят изменения. Когда, со временем, проект растёт, разработчики, благодаря использованию модульных тестов, могут спокойно добавлять в него новые возможности и при этом не опасаться того, что нововведения нарушат работу уже существующих механизмов. Для этого достаточно лишь следовать практике написания модульных тестов с самого начала работы над проектом.

Если говорить о модульных тестах, применяемых в проектах, основанных на Vue, то можно сказать, что такие тесты практически идентичны тем, что применяются в проектах, основанных на других фреймворках и библиотеках. А именно, с Vue хорошо сочетаются Jest, Karma или Mocha. Но, независимо от того, какой именно фреймворк выбран для создания тестов, есть несколько общих рекомендаций, которые следует учитывать, разрабатывая модульные тесты:

  • Если тест завершился неудачно — он должен выводить чёткие сообщения об ошибках.
  • При создании тестов рекомендуется использовать хорошие библиотеки для формирования утверждений. (Например, механизмы для формирования утверждений встроены в Jest, а с Mocha используется библиотека Chai).
  • Модульные тесты должны покрывать все компоненты Vue.

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

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

Итоги


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

Как вы думаете, подходит ли Vue для разработки крупномасштабных проектов?

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 28

    –2

    Не то, чтобы я разрабатывал действительно большой Vue проект, но два с половиной года на одном проекте так вот оттарабанил.


    Главные уроки оттуда:


    1. Не используйте vuex, даже если вы сейчас понимаете, как он работает. После отпуска вы гарантированно vuex забудете, а если кто-то ещё придёт на проект, то он точно поймёт vuex не так и всё испортит.


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


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


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

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

        Неужели придется во vuex все держать? Потенциально сотни строк данных?
          0
          Сотни строк данных это не самый большой объем, вообще говоря.
          Я на своем петпроекте обычно держу в состоянии приложения только срез данных. Например, если у меня есть табличка с юзерами, то подгружается она постранично, и если из вебсокетов приходит сообщение об изменении пользователя (например, он вошел в сеть), то в хранилище вносятся изменения только если пользователь есть на этой условной странице.
          Аналогично ведется работа с другими многочисленными сущностями — комментариями, новостями итд. Приложение и его стор не должны превращаться в копию бд на клиенте.
            0
            Не претендую на правильность, так как новичок, да и Вью использую, в основном, для собственных проектов, но для себя подобную логику выношу в отдельный модуль (например класс), дальше передаю этот модуль через vuex (достаточно просто стейта). Ну и в других компонентах мы точно получаем доступ к одному и тому же экземпляру класса.
            +4
            Эм что?
            1. No comments. С таким подходом не пишите код. Завтра вы его всё равно забудете.
            2. Он в отличии от других подобных плюшек собирает себя только в тех рамках в которых вы его используете. Не нравится часть компонентов, просто не используй. В конечном бандле они не будут принимать участия.
            3. Прямо сейчас работаю с Канадцами и меня всё ещё заставляют писать под IE 11 и IOS9. Это экономически обосновано. Нам всем надо с рынка уходить и оставить клиентов на таких как вы, «Купи новый айфон, ты чё как бомж»? И это на секундочку Канада.
              +2

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

                0
                Если в проекте намешаны два подхода, то понятно, что будет беда. Но вот у меня на проекте vuex совсем не прижился. Совершенно другая философия у него, в отличии от vue. Был полностью выпилен и сейчас без него прикрасно живётся. Компонентов много, в роутинге под 300 типов страниц, эндпоинтов очень много, всё прекрасно живёт без vuex.
                  0

                  Хм. А что используется в место него? И как вообще устроена архитектура?

                    0
                    Смею предположить, что используются providers.
                    0
                    Используется шина данных в виде инстанса vue. Там ооочень мало данных, которые нужны прямо всем приложениям. Я сторонник подхода, чтобы состояние хранилось в урле и из неё же инициализировалось. Остальное мы либо храним в шине (например очередб уведомлений) либо не храним вообще (типа недозаполненный инпут у неотправленной формы)
                      0
                      Ну если у вас данных столько что можно хранить в урле, то это один разговор. И понятно что махину подключать ради 2х параметров не стоит. Я думаю мы все тут (по умолчанию) думали об ситуациях чуток по сложнее.
              +3
              Я бы еще добавил — сразу используйте тайпскрипт.
                0
                Попытался я как-то вьюшный проект на тс перевести. Больше не буду пытаться.
                Жду выхода вью3, чтобы уже точно можно было на прод катить, и обязательно буду на него всё переписывать уже с нормальной поддержкой тайпскрипта. Без тс прям тоскливо.
                  +1
                  А какие сложности основные? Два года назад взяли связку TS + Vue и сейчас весь проект на ней.
                    0
                    Недавно перевел средних размеров проект (страниц штук 40, своя библиотека базовых компонентов) на тайпскрипт и стайлус. Ничего особенно сложного, а поддержка тайпскрипта вполне бодрая.
                    Единственное что меня как олда подбешивает — это отсутствие хоть одной удобной тулзы для валидации форм. После, простите, jquery.unobtrusive.validate все выглядит громоздким и при этом черновым.
                      0
                      Используем vee-validate, в целом всё устраивает
                        0

                        Пробовал его и не понравилось, но сейчас в доках вижу что-то новое. Спасибо, попробую.

                      +1
                      А в чем конкретно проблема была?
                      Писали несколько крупных приложений с 0 на тс — все было отлично. Переводить, правда, не пробовали…
                        +1
                        С typescript есть куча приятных мелочей, например vuex-module-decorators.

                        Стор:

                        import Vue from 'vue';
                        import Vuex from 'vuex';
                        import VuexPersistence from 'vuex-persist';
                        
                        import { AppState } from 'src/store/modules/app';
                        
                        Vue.use(Vuex);
                        
                        export interface RootState {
                          'app': AppState
                        }
                        
                        const vuexLocal = new VuexPersistence<RootState>({
                          storage: window.localStorage,
                          reducer: (state) => ({
                            app: state.app,
                          }),
                        });
                        
                        const store = new Vuex.Store<RootState>({
                          strict: process.env.DEV === 'true',
                          plugins: [vuexLocal.plugin],
                        });
                        
                        export function checkModuleStateExists(moduleName: string): boolean {
                          const savedState = localStorage.getItem('vuex');
                          if (!savedState) {
                            return false;
                          }
                        
                          return JSON.parse(savedState)[moduleName] != null;
                        }
                        
                        export default store;
                        
                        


                        Модуль:

                        export interface AppState {
                          drawerIsOpen: boolean
                        }
                        
                        @Module({
                          dynamic: true,
                          name: 'app',
                          namespaced: true,
                          stateFactory: true,
                          store: Store,
                          preserveState: checkModuleStateExists('app'),
                        })
                        export class AppStoreModule extends VuexModule implements AppState {
                          public drawerIsOpen: boolean = true;
                        }
                        
                        export default getModule(AppStoreModule);
                        


                        В любом компоненте можно получить доступ так:

                        <script lang="ts">
                          import { Vue, Component } from 'vue-property-decorator';
                          import AppModule, { AppStoreModule } from 'src/store/modules/app';
                          import { getModule } from 'vuex-module-decorators';
                        
                          @Component
                          export default class MainLayout extends Vue {
                            app = getModule(AppStoreModule);
                            
                            get drawerIsOpen(): boolean {
                              return AppModule.drawerIsOpen;
                              /// или
                              return this.app.drawerIsOpen;
                            }
                          }
                        </script>
                        
                        
                          0
                          Vue 2 весьма плохо оптимизирован по TS. Настоятельно рекомендую быстрее переходить на Composition API еще до выхода Vue 3, благо на это есть библиотека от создателей.

                          Намного проще и удобнее выносить все в чистые функции TS и просто импортировать в Vue 3 компоненты.
                            0
                            Не очень понимаю что значит плохо оптимизирован по ts, если всё равно всё в js соберётся. Классы опять же никуда не денусть и в тройке можно будет так же писать. (Отказались от своего Class API предложения)
                            Ну и мне проще работать с классами как я это делаю в других языках.
                      +1

                      Структуру VueX можно настроить более детально. Например: не хранить все геттеры в одном файле, а делить их в зависимости от функционала (store-folder->news-folder->news-getters.js). Тоже самое и с экшенами, мутациями и состояниями, так будет удобнее управлять хранилищем и не копаться в одном файле, где овер 5к строк, чтобы найти необходимое

                        +1
                        А можно у стора сделать namespaced: true,:
                        export default {
                            namespaced: true,
                            state: {},
                            mutations: {},
                            actions: {},
                            getters: {}
                        }
                        

                        и подрубать через
                        import Articles from './modules/articles';
                        export default new Vuex.Store({
                            modules: {
                                Articles 
                            }
                        })
                        

                        и юзать:
                        
                        ...mapGetters('Articles', ['getter1', 'getter2']),
                        ...mapActions('Articles', ['action1', 'action2'])
                        ...mapMutations('Articles', ['mutation1', 'mutation2'])
                        

                        ну а если в разных сторах совпадают геттеры/мутации или экшоны, то можно так:
                        
                        ...mapActions('Articles', {
                           loadArticles: 'load',   
                        }),
                        ...mapActions('Categories', {
                           loadCategories: 'load',   
                        }),
                        


                        и обращаться соответственно
                        
                        this.loadArticles();
                        this.loadCategories();
                        
                        0
                        Тот случай, когда комментарии полезнее статьи.

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

                        Самое читаемое