Skip to content

Архитектурные решения

Подходящая архитектура для Vue 3 веб-приложения

Самая удобная архитектура для Vue 3 приложения - модульная.

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

Кроме того, есть модуль оболочки - то, что иногда называет app shell. Шаблон с хедером, футером, боковым меню и главным окном. В этот модуль целесообразно поместить утилиты и ресурсы, используемые по всему приложению - например, api, useI18n, BaseButton и stringHelpers.

В каждом модуле свои components, composables, assets, utils; возможно, api, routes, views, layouts.

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

image

Как переиспользовать одни те же разрабатываемые ресурсы на нескольких проектах?

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

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

Если нужна версионность и независимость библиотеки ресурсов, можно оформить её как private NPM пакет.

Когда использовать Provide/Inject?

Use cases

  • Настройки темы или конфигурации: Вы можете предоставить глобальные настройки, темы или параметры конфигурации на корневом уровне вашего приложения Vue.js и внедрить их в любой компонент, которому они нужны.

  • Данные аутентификации: Предоставляйте информацию об аутентификации пользователя, например данные пользователя или токены аутентификации, компонентам, которым они необходимы, не передавая их через пропсы.

  • Интернационализация (i18n): Храните данные перевода на корневом уровне и внедряйте их в компоненты, которым требуется отображать текст на разных языках.

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

Лучшие практики и соображения

При использовании provide и inject необходимо помнить о нескольких лучших практиках:

  • Избегайте чрезмерного использования: Хотя provide и inject могут быть очень мощными, не стоит ими злоупотреблять. Оставьте их для случаев, когда вам действительно нужно поделиться данными между компонентами, не создавая тесной связи между ними.

  • Четкая документация: Документируйте данные, которые вы провайдите и инжектируете, чтобы разработчикам было легко понять, как взаимодействуют компоненты. Их также можно типизировать, - подробности в официальной документации.

  • Рассмотрите возможность Prop Drilling: Prop drilling обычно является антипаттерном, но в некоторых случаях передача данных через пропсы может быть более простым и прозрачным подходом. Оцените, являются ли provide и inject лучшим решением для вашего конкретного сценария.

Как лучше организовать работу с лэйаутами (макетами)?

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

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

Пример:

vue
// MainLayout
<template>
  <div>
    <AppHeader />
    <router-view />
    <AppFooter />
  </div>
</template>

// SidebarLayout
<template>
  <div>
    <AppHeader />
    <div>
        <AppSidebar />
        <router-view />
    </div>
    <AppFooter />
  </div>
</template>

// App.vue
<template>
  <router-view />
</template>

routes = generalRoutes U accountRoutes

js
export const generalRoutes = {
  path: "/",
  component: MainLayout,
  children: [
    {
      path: "/maintenance",
      name: "maintenance",
      component: MaintenanceView,
    },
  ]
};
// ...

export const accountRoutes = {
  path: "/account",
  component: SidebarLayout,
  children: [
    {
      path: "",
      name: "account",
      component: UserAccountView,
      meta: { requiresAuth: true, backRoute: "/" },
    }
  ]
};
Как сделать один глобальный прелоадер (аналог Suspense)?

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

AppLoader.vue
vue
<script setup>
import { useAppLoader } from "@/app/composables/useAppLoader";
const { loading } = useAppLoader();
</script>

<template>
  <div class="loader" :class="{ active: loading }">
    <div class="loaderBar" />
  </div>
</template>
useAppLoader.ts
ts
import { computed, reactive, ref } from "vue";
import { uuid } from "@/app/utils/uuid";

const loaderSet = reactive(new Set<string>());
const loading = computed(() => loaderSet.size > 0);

export function useAppLoader() {
  const _uuid: string = uuid();

  function startLoading() {
    loaderSet.add(_uuid);
  }
  function stopLoading() {
    loaderSet.delete(_uuid);
  }

  return { loading, startLoading, stopLoading };
}
SomeComponent
js
import { useAppLoader } from "@/app/composables/useAppLoader";
const { startLoading, stopLoading } = useAppLoader();

startLoading();
product.value = await api.products.product(props.productId);
stopLoading();

uuid - любая функция для генерации уникального id.