VUE 3

Архитектура

Архитектура

Что такое Pinia

Pinia — что это?

Pinia — это хранилище состояния (state manager) для Vue.
Проще:

Pinia = глобальное хранилище данных приложения


Зачем нужна Pinia

Когда нужно:

Примеры данных в Pinia:


Pinia vs Vuex

Vuex Pinia
Старый стандарт Новый стандарт ✅
Сложный синтаксис Простой
Много boilerplate Минимум кода
Vue 2 / 3 Vue 3 (рекомендуется)

👉 Pinia официально заменила Vuex


Простой пример Pinia

1️⃣ Store

// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'Alex',
    isAuth: false
  }),

  actions: {
    login(name: string) {
      this.name = name
      this.isAuth = true
    }
  }
})

2️⃣ Использование в компоненте

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

userStore.login('Ivan')
</script>

<template>
  <div>{{ userStore.name }}</div>
</template>

Где подключается Pinia

В main.ts:

import { createPinia } from 'pinia'

const pinia = createPinia()

createApp(App)
  .use(pinia)
  .mount('#app')

В Nuxt 3

Pinia подключается автоматически:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
})

Когда Pinia НЕ нужна

❌ Маленький проект
❌ 2–3 компонента
❌ Нет общего состояния


Коротко


Архитектура

Что такое Nuxt

Nuxt — это фреймворк поверх Vue, который упрощает разработку приложений и сайтов.

Если коротко:

Vue — это библиотека
Nuxt — это готовая архитектура + инструменты для Vue


Зачем нужен Nuxt

Nuxt решает типичные проблемы Vue-приложений:

✅ Роутинг из коробки

Не нужно настраивать vue-router.

📁

pages/index.vue   → /
pages/about.vue   → /about
pages/user/_id.vue → /user/:id

✅ Server-Side Rendering (SSR)

Страницы могут рендериться:

Это важно для:


✅ Автоимпорт

Не нужно вручную импортировать:

<script setup>
const route = useRoute() // работает без import
</script>

✅ Работа с API

Встроенные:

// server/api/users.ts
export default () => {
  return [{ id: 1, name: 'Alex' }]
}

Чем Nuxt отличается от обычного Vue

Vue Nuxt
Сам настраиваешь проект Готовая структура
Роуты вручную Роуты по папке pages
SPA по умолчанию SPA / SSR / SSG
Больше конфигурации Меньше рутины

Когда использовать Nuxt

✔ Лендинги
✔ SEO-проекты
✔ Интернет-магазины
✔ Корпоративные сайты
✔ Fullstack (frontend + backend)


Когда Nuxt не нужен

❌ Маленькие SPA
❌ Админки без SEO
❌ Если важен минимальный размер бандла


Версии

👉 В 2026 году актуален Nuxt 3


Пример структуры Nuxt 3

pages/
layouts/
components/
composables/
server/
nuxt.config.ts

 

Архитектура

Где находится vue-router

vue-router не лежит в твоём проекте напрямую — это npm-пакет, который устанавливается как зависимость.

Разберём по шагам 👇


Где физически находится vue-router

После установки он лежит здесь:

node_modules/vue-router/

То есть импорт:

import { createRouter } from 'vue-router'

читается как:

«Возьми пакет vue-router из node_modules»


Откуда он берётся

1️⃣ Он указан в package.json

{
  "dependencies": {
    "vue": "^3.x.x",
    "vue-router": "^4.x.x"
  }
}

2️⃣ Устанавливается через npm / yarn / pnpm

npm install vue-router
# или
pnpm add vue-router

Как JS понимает vue-router

Алгоритм такой:

  1. Видит импорт 'vue-router'

  2. Ищет папку:

    node_modules/vue-router
    
  3. Смотрит package.json внутри пакета

  4. Берёт entry point (обычно dist/vue-router.mjs)


Что внутри vue-router

Примерно так:

node_modules/
  vue-router/
    dist/
      vue-router.mjs
      vue-router.cjs
    package.json

Именно из dist экспортируются:


Важный момент 🔥

import './router'

⬆️ это локальный файл

import 'vue-router'

⬆️ это npm-пакет


В Nuxt

Ты не импортируешь vue-router вручную:


Коротко

vue-router — это пакет из node_modules, подключённый через package.json

 

Архитектура

Что такое ConfigProvider

Это корневой шаблон приложения, где оборачивается весь UI и подключаются глобальные настройки. Разберём по слоям, сверху вниз 👇


Общая идея

<ConfigProvider>
  <App>
    <RouterView />
  </App>
</ConfigProvider>

👉 Всё приложение:


1️⃣ <ConfigProvider> — глобальная конфигурация Ant Design

<ConfigProvider :locale="antdLocale" :theme="tokenTheme">

Это компонент из Ant Design Vue.

Что он делает:

Пример:

antdLocale = ruRU
tokenTheme = {
  token: { colorPrimary: '#1677ff' }
}

Без него:


2️⃣ <App> — layout / оболочка приложения

⚠️ Это не App.vue, а компонент Ant Design Vue:

import { App } from 'ant-design-vue'

Он:

Пример использования:

const { message } = App.useApp()
message.success('Успешно')

📌 По сути — служебный контейнер Ant Design.


3️⃣ <RouterView /> — место для страниц

<RouterView />

Это компонент Vue Router.

Он:

Пример:


Как это работает вместе

Пусть пользователь открывает:

/users

Тогда Vue:

  1. Загружает App.vue

  2. Применяет Ant Design настройки

  3. Находит маршрут /users

  4. Вставляет Users.vue внутрь <RouterView />

  5. Весь UI:

    • с нужной темой

    • на нужном языке

    • в общем layout


Визуально

ConfigProvider (locale, theme)
 └─ App (Ant Design context)
     └─ RouterView
         └─ Users.vue

Коротко

Элемент Что делает
ConfigProvider Глобальные настройки AntD
App Контекст AntD (message, modal)
RouterView Отрисовывает текущую страницу

Частая ошибка ❌

Путать:

 

Архитектура

Что означает app.mount('#app');

app.mount('#app') — это момент, когда Vue реально запускается и начинает рисовать интерфейс.


Что именно означает

app.mount('#app')

Vue говорит:

«Найди элемент с id app в HTML
и вставь туда корневой Vue-компонент»


Откуда берётся #app

В index.html:

<body>
  <div id="app"></div>
</body>

#app — это обычный CSS-селектор.


Что происходит при mount

1️⃣ Vue находит <div id="app">
2️⃣ Создаёт экземпляр приложения
3️⃣ Рендерит App.vue
4️⃣ Подключает реактивность
5️⃣ Запускает жизненный цикл компонентов

После этого:


До mount

const app = createApp(App)

На этом этапе:

app.use(router)
app.use(pinia)

После mount

❌ Нельзя:

app.mount('#app')
// app.use(router) ❌ уже поздно

Если #app не найден

Vue выдаст ошибку:

Failed to mount app: mount target selector "#app" returned null

Аналогия 🧠


Коротко

app.mount('#app')
👉 привязывает Vue-приложение к DOM

 

Архитектура

hash vs history

1️⃣ Hash mode (#/path)

Пример URL:

http://site.com/#/modules

Как работает

Плюсы ✅

✔ Работает без настройки сервера
✔ Можно открыть страницу напрямую
✔ Подходит для GitHub Pages, static hosting

Минусы ❌

✖ URL выглядит «грязно» (#)
✖ Хуже для SEO

Как включается

createWebHashHistory()

2️⃣ History mode (/path)

Пример URL:

http://site.com/modules

Как работает

Плюсы ✅

✔ Красивые URL
✔ Лучше для SEO
✔ Стандарт для продакшена

Минусы ❌

✖ Нужна настройка сервера
✖ Без неё будет 404 при обновлении

Как включается

createWebHistory()

Главное различие 🔥

  Hash History
URL #/modules /modules
Сервер Не нужен Нужен
SEO Плохо Хорошо
Обновление страницы Всегда ок 404 без конфига
Продакшен Редко Чаще всего

Когда что использовать

🔹 Hash — если:

🔹 History — если:


Пример настройки сервера (Nginx)

location / {
  try_files $uri $uri/ /index.html;
}

В Nuxt


Коротко

Hash — просто и без сервера
History — красиво и правильно, но требует настройки

 

Архитектура

Где будет #/modules

#/modules не имеет отношения к папке ./modules в файловой системе.
Это URL-путь, а не путь к файлам.


Что такое #/modules

# в URL означает hash-роутинг.

Пример адреса в браузере:

http://localhost:5173/#/modules

Это значит:


Где это «находится»

👉 Нигде в файловой системе напрямую

#/modules:


Как это связано с роутами

Где-то в коде есть маршрут:

{
  path: '/modules',
  component: ModulesView
}

Или он создаётся динамически, например так 👇

import.meta.glob('./modules/**/*.ts', { eager: true })

Каждый файл может экспортировать роуты, например:

// modules/modules.ts
export default {
  path: '/modules',
  component: () => import('@/views/Modules.vue')
}

Почему есть #

Если в router используется:

createWebHashHistory()

То URL будет:

#/modules

Если бы было:

createWebHistory()

То было бы:

/modules

Важное различие 🔥

Что Пример Где живёт
Папка ./modules в проекте
Роут #/modules в браузере
Компонент Modules.vue src/views

Коротко

./modulesпапка с кодом
#/modulesмаршрут в браузере

Они могут быть связаны логически, но это разные вещи.

 

Архитектура

Где находится ./modules/

Тут важно понять относительный путь 👍

import.meta.glob('./modules/**/*.ts')

Где именно находится ./modules

./modules — это путь относительно файла, в котором написана эта строка.

Не от src и не от корня проекта, а от текущего файла.


Пример 1 — файл лежит в router/index.ts

src/
  router/
    index.ts   ← здесь код
    modules/
      auth.ts
      admin.ts

Тогда:

./modules/ → src/router/modules/

Пример 2 — файл в src/index.ts

src/
  index.ts    ← здесь код
  modules/
    user.ts

Тогда:

./modules/ → src/modules/

Как быстро проверить в проекте

1️⃣ Найди файл с import.meta.glob

Обычно это:

2️⃣ Посмотри структуру папок рядом

Папка modules должна быть рядом с этим файлом.


Частая ошибка ❌

Ожидать, что путь считается от src:

// ❌ НЕ от корня проекта
import.meta.glob('/modules/**/*.ts')

Если бы хотели от src

Нужно использовать alias:

import.meta.glob('@/modules/**/*.ts')

(где @src)


Итог

./modules — это папка на одном уровне с файлом, в котором написан код.

 

Архитектура

Что здесь происходит: const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { eager: true, });

Разберём построчно 

const dynamicRouteFiles = import.meta.glob(
  './modules/**/*.ts',
  { eager: true }
)

Что это такое в целом

Это Vite-фича (не чистый JS и не Vue), которая:

🔍 находит все .ts файлы в папке modules и импортирует их


Подробно

1️⃣ import.meta.glob(...)

Это специальная функция Vite:

import.meta.glob('./modules/**/*.ts')

означает:

📁 Пример структуры:

modules/
  auth.ts
  admin/users.ts
  admin/roles.ts

2️⃣ Что вернёт import.meta.glob

Без eager:

{
  './modules/auth.ts': () => import('./modules/auth.ts'),
  './modules/admin/users.ts': () => import('./modules/admin/users.ts'),
}

⚠️ Файлы не загружаются сразу, а лениво.


3️⃣ { eager: true }

{ eager: true }

означает:

📦 импортировать файлы сразу при запуске

Теперь результат будет:

{
  './modules/auth.ts': { default: ... },
  './modules/admin/users.ts': { default: ... },
}

То есть:


Зачем это обычно используют

🔹 Динамическая регистрация

Чаще всего:

Пример с роутами:

const routes = []

Object.values(dynamicRouteFiles).forEach((module: any) => {
  routes.push(...module.default)
})

Каждый файл:

// modules/admin.ts
export default [
  { path: '/admin', component: Admin }
]

Почему так делают

✔ Не нужно вручную импортировать каждый файл
✔ Просто добавил файл — он автоматически подключился
✔ Удобно для больших проектов


Важно помнить ⚠️


Коротко

Эта строка автоматически импортирует все .ts файлы из modules и подкаталогов при старте приложения.

 

Архитектура

Что выполняется первым main.ts или app.vue

Коротко:
👉 первым выполняется main.ts, а уже потом — App.vue.


Порядок выполнения во Vue

1️⃣ main.tsточка входа

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

Пример:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Что здесь происходит:


2️⃣ App.vueкорневой компонент

App.vue начинает выполняться после mount().

<script setup>
console.log('App.vue')
</script>

Если смотреть по жизненному циклу

Пример логов:

// main.ts
console.log('main.ts')

createApp(App).mount('#app')
<script setup>
console.log('App.vue setup')
</script>

📌 В консоли будет:

main.ts
App.vue setup

Важно понимать


Частые случаи

🔹 Где что писать

Что нужно сделать Где
Подключить router main.ts
Подключить pinia main.ts
Глобальные стили main.ts
Логика UI App.vue
<router-view /> App.vue

🔹 В Nuxt

Там нет main.ts:

 

Архитектура

Где в VUE прописаны роуты?

В Vue роуты прописываются не в самом компоненте, а в Vue Router — обычно в отдельном файле.

Где именно смотреть 👇

1️⃣ Vue 2 / Vue 3 (самый частый вариант)

Обычно файл:

src/router/index.js

или

src/router/index.ts

Пример:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    component: () => import('@/views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

2️⃣ Где роутер подключается к приложению

Обычно в:

src/main.js

или

src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

3️⃣ Если используется Nuxt

Тогда роуты не прописываются вручную:

pages/

Структура папок = маршруты
Например:

pages/index.vue   → /
pages/login.vue   → /login
pages/user/_id.vue → /user/:id

4️⃣ Как быстро понять в чужом проекте

"vue-router"

 

Архитектура

что делает конструкция: router.beforeEach((to) => { ... });

router.beforeEach((to) => { ... }) — это глобальный навигационный гард (guard) во Vue Router.

Проще:

👉 код, который выполняется ПЕРЕД каждым переходом по роуту


Что именно происходит

router.beforeEach((to) => {
  // ...
})

Каждый раз, когда пользователь:

Vue Router:
1️⃣ собирается перейти на новый маршрут
2️⃣ вызывает beforeEach
3️⃣ ждёт его результат
4️⃣ либо продолжает переход, либо отменяет / перенаправляет


Параметры

Полная сигнатура:

router.beforeEach((to, from) => {})

Чаще используют to.


Что можно делать внутри

✅ Разрешить переход

return true

или ничего не возвращать


🔁 Перенаправить

return { path: '/login' }

⛔ Запретить

return false

Типичный пример — авторизация 🔐

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isAuth()) {
    return { path: '/login' }
  }
})
{
  path: '/profile',
  component: Profile,
  meta: { requiresAuth: true }
}

Когда именно выполняется

⏱ Порядок:

  1. beforeEach

  2. beforeResolve

  3. afterEach

📌 beforeEachсамый ранний хук


Частые кейсы использования

router.beforeEach((to) => {
  document.title = to.meta.title || 'App'
})

Важно ⚠️

router.beforeEach(async (to) => {
  await checkAuth()
})

Коротко

router.beforeEach — это фильтр маршрутов, который срабатывает перед каждым переходом

 

Архитектура

Что означает конструкция: const loadedPaths = new Set<string>();

Эта конструкция означает:
👉 создаётся пустое множество (Set) строк, которое будет хранить уникальные значения.

Разберём по частям 👇


Разбор строки

const loadedPaths = new Set<string>();

1️⃣ Set

Set — это встроенная структура данных в JavaScript.

Она:

Пример:

const s = new Set();
s.add('/login');
s.add('/login');
console.log(s.size); // 1

2️⃣ <string>

Это TypeScript-тип.

Set<string>

означает:

loadedPaths.add('/users'); // ✅
loadedPaths.add(123);      // ❌ ошибка TS

3️⃣ new Set()

Создаёт новый пустой Set.

На старте:

loadedPaths.size === 0

Зачем это обычно используют

🔹 Отслеживать уже обработанные маршруты

if (!loadedPaths.has(to.path)) {
  loadRoute(to.path)
  loadedPaths.add(to.path)
}

🔹 Не выполнять один и тот же код дважды

if (loadedPaths.has(key)) return

🔹 Кешировать результаты

loadedPaths.add('/profile')

Почему не массив (string[])

Set Array
Гарантирует уникальность Нужно проверять вручную
Быстро has() Медленно includes()
Нет дубликатов Легко ошибиться

В контексте vue-router (часто)

const loadedPaths = new Set<string>();

router.beforeEach((to) => {
  if (loadedPaths.has(to.path)) return

  loadedPaths.add(to.path)
  // динамическая загрузка роутов
})

Коротко

const loadedPaths = new Set<string>();
👉 хранилище уникальных строк, обычно для контроля «что уже было обработано»

 

Архитектура

Что делает эта строка: const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');

Это ключевая строка для динамических страниц 👇

const pageMap: ComponentRecordType =
  import.meta.glob('../views/**/*.vue');

Коротко

Эта строка находит все .vue файлы в папке views (на один уровень выше текущего файла) и создаёт карту компонентов,
но не загружает их сразу (ленивая загрузка).


Разбор по частям

1️⃣ import.meta.glob(...)

Это Vite-функция.

Она:


2️⃣ '../views/**/*.vue'

Это glob-шаблон:

📁 Пример:

views/
  Home.vue
  user/Profile.vue
  admin/Users.vue

3️⃣ Что именно вернёт import.meta.glob

Пример результата:

{
  '../views/Home.vue': () => import('../views/Home.vue'),
  '../views/user/Profile.vue': () => import('../views/user/Profile.vue'),
  '../views/admin/Users.vue': () => import('../views/admin/Users.vue'),
}

📌 ВАЖНО:


4️⃣ ComponentRecordType

Это TypeScript-тип.

Обычно выглядит так:

type ComponentRecordType = Record<string, () => Promise<Component>>

Он говорит:


Зачем это используют

🔹 Динамический роутинг

const routes = Object.keys(pageMap).map((path) => {
  return {
    path: convertToRoute(path),
    component: pageMap[path],
  }
})

👉 Каждый .vue файл автоматически становится страницей.


🔹 Code splitting

Компонент загрузится только когда пользователь зайдёт на страницу.


Отличие от { eager: true }

Без eager С eager
Ленивая загрузка Загрузка сразу
Меньше initial bundle Быстрее первый переход
Лучше для больших проектов Подходит для мелких

Частая ошибка ❌

Ожидать, что компонент уже доступен:

pageMap['../views/Home.vue'] // ❌ это функция

Правильно:

const comp = await pageMap['../views/Home.vue']()

Коротко

Эта строка создаёт карту Vue-страниц из папки views
и позволяет лениво загружать компоненты

 

Архитектура

Что такое Vite?

Vite — это инструмент для разработки и сборки фронтенд-приложений (build tool), чаще всего для Vue, но не только.

Проще всего:

Vite = dev-сервер + сборщик, который делает разработку быстрой.


Зачем нужен Vite

Он решает главную боль старых сборщиков (Webpack):


Как работает Vite (идея)

🔹 В режиме разработки

Vite не собирает весь проект.

Он:

👉 поэтому запуск и HMR почти мгновенные.


🔹 В продакшене

Vite:


Почему Vite такой быстрый

Старые сборщики Vite
Собирают всё сразу Сборка «по требованию»
Большой initial bundle ES modules
Медленный HMR Мгновенный HMR

Что даёт Vite разработчику

✔ Мгновенный запуск (npm run dev)
✔ Быстрый Hot Reload
✔ Простая конфигурация
✔ Отлично работает с Vue 3 и TypeScript
✔ Современный стандарт де-факто


Важная фича Vite (ты уже видел 👇)

import.meta.glob('./modules/**/*.ts')

👉 это фича Vite, не JavaScript и не Vue.


Где используется Vite


Файлы Vite в проекте

vite.config.ts
index.html
src/

Пример:

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()]
})

Vite vs Webpack (коротко)

  Vite Webpack
Скорость ⚡ очень быстро 🐢 медленно
Конфиг простой сложный
Современность ES modules legacy
Новый Vue ✅ стандарт ❌ почти не используют

Коротко

Vite — современный инструмент сборки,
который делает разработку быстрой и простой.

Архитектура

Что делает эта строка: return requestClient.get<RouteRecordStringComponent[]>('/menu/all');

Эта строка делает HTTP-запрос к серверу и возвращает результат.
Разберём точно и по слоям 👇

return requestClient.get<RouteRecordStringComponent[]>('/menu/all');

Коротко

👉 Отправляет GET-запрос на /menu/all
👉 Ожидает ответ в виде массива маршрутов
👉 Возвращает Promise с этими данными


Разбор по частям

1️⃣ requestClient

Это HTTP-клиент, чаще всего:

Пример:

const requestClient = axios.create({
  baseURL: '/api'
})

2️⃣ .get(...)

Метод:

requestClient.get('/menu/all')

3️⃣ '/menu/all'

Это endpoint API.

Полный реальный URL может быть:

/api/menu/all
https://example.com/menu/all

Сервер обычно возвращает:

[
  {
    "path": "/users",
    "component": "Users",
    "meta": { "title": "Пользователи" }
  }
]

4️⃣ <RouteRecordStringComponent[]>

Это TypeScript-тип ответа.

Он означает:

Пример такого типа:

type RouteRecordStringComponent = {
  path: string
  component: string
  meta?: Record<string, any>
}

📌 Это только для TypeScript, в рантайме его нет.


5️⃣ return

Функция возвращает Promise.

const res = await getMenu()

или

getMenu().then(res => { ... })

Как это обычно используется (очень типично)

async function loadMenu() {
  const routes = await requestClient.get<RouteRecordStringComponent[]>('/menu/all')
  addDynamicRoutes(routes)
}

👉 Часто:


Что реально происходит по шагам

1️⃣ Фронт вызывает функцию
2️⃣ Отправляется GET /menu/all
3️⃣ Сервер возвращает JSON
4️⃣ TypeScript проверяет тип
5️⃣ Данные возвращаются вызывающему коду


Коротко

Эта строка получает список маршрутов / меню с сервера
и возвращает его как Promise с типизацией.

Архитектура

Что означает конструкция: badgeVariants?: | 'default' | 'destructive' | 'primary' | 'success' | 'warning' | string;

Это TypeScript-конструкция, которая описывает тип свойства.
По шагам 👇


Что это означает в целом

badgeVariants?:
  | 'default'
  | 'destructive'
  | 'primary'
  | 'success'
  | 'warning'
  | string;

👉 Это описание опционального поля badgeVariants,
которое может принимать одно из перечисленных значений.


Разбор по частям

1️⃣ badgeVariants?

Знак ? означает:

🔹 свойство необязательное

То есть объект может не содержать badgeVariants.

{} // ✅ ок

2️⃣ |

Это union type (объединение типов).

Значение может быть одним из вариантов.


3️⃣ Строковые литералы

'default'
'destructive'
'primary'
'success'
'warning'

Это конкретные допустимые значения строк.

badgeVariants: 'success' // ✅
badgeVariants: 'info'    // ❌ (если бы не было string)

4️⃣ | string

Это означает:

🔥 или любая другая строка

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

badgeVariants: 'info'       // ✅
badgeVariants: 'my-custom' // ✅

Зачем так делают

🔹 Подсказки + гибкость


🔹 Часто используется для UI

Например:

<Badge variant="success" />
<Badge variant="my-theme-variant" />

Важный момент ⚠️

С точки зрения TypeScript:

'default' | 'primary' | string

👉 эквивалентно просто string

НО:


Если бы хотели строго ограничить

badgeVariants?: 
  | 'default'
  | 'destructive'
  | 'primary'
  | 'success'
  | 'warning';

Тогда:

badgeVariants: 'info' // ❌ ошибка

Коротко

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

Архитектура

Что такое Promise?

Promise — это объект в JavaScript, который представляет результат асинхронной операции
(то есть того, что завершится позже).

Проще:

Promise = обещание, что значение будет, но не сразу


Зачем нужен Promise

В JS много асинхронных вещей:

Promise позволяет:


Как выглядит Promise

const promise = new Promise((resolve, reject) => {
  // асинхронная работа
})

У Promise есть 3 состояния:

Состояние Что значит
pending в процессе
fulfilled успешно выполнен
rejected ошибка

Простой пример

const promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('Готово!')
  }, 1000)
})

Через 1 секунду Promise станет fulfilled.


Как использовать Promise

1️⃣ .then() / .catch()

promise
  .then(result => {
    console.log(result)
  })
  .catch(error => {
    console.error(error)
  })

2️⃣ async / await (современно 👍)

async function run() {
  const result = await promise
  console.log(result)
}

👉 await:


Реальный пример (ты уже видел)

requestClient.get('/menu/all')

Возвращает:

Promise<Menu[]>

Использование:

const menu = await getAllMenusApi()

Что происходит под капотом

console.log('1')

fetch('/api')
  .then(() => console.log('2'))

console.log('3')

Вывод:

1
3
2

Promise выполняется позже, когда стек освободится.


Promise vs синхронный код

Синхронный Асинхронный (Promise)
Блокирует выполнение Не блокирует
Сразу возвращает значение Возвращает Promise
Просто Гибко и эффективно

Частые ошибки ❌

🔹 Забыли await

const data = getAllMenusApi() // ❌ Promise

🔹 Нужно так:

const data = await getAllMenusApi() // ✅ данные

Коротко

Promise — это механизм работы с асинхронным кодом в JavaScript:
результат будет позже, и JS знает, когда и как его обработать.

Архитектура

Что означает: type RouteRecordStringComponent<T = string> = Omit< RouteRecordRaw, 'children' | 'component' > & { children?: RouteRecordStringComponent<T>[]; component: T; };

Это TypeScript-тип, который переопределяет тип маршрута Vue Router, заменяя поле component на строку (или другой тип).

Общий смысл

RouteRecordStringComponent — это описание маршрута,
где componentНЕ Vue-компонент, а строка (обычно имя компонента или путь).

Такой тип часто используют, когда:


Разбор кода

type RouteRecordStringComponent<T = string> = ...

1️⃣ T = string

Это generic-тип с дефолтным значением:

Примеры:

RouteRecordStringComponent        // component: string
RouteRecordStringComponent<() => Promise<Component>>

2️⃣ Omit<RouteRecordRaw, 'children' | 'component'>

RouteRecordRaw — стандартный тип маршрута из vue-router.

Omit:

убирает поля children и component

То есть берётся всё остальное:


3️⃣ & { ... }

Это расширение типа.

Мы добавляем свои версии children и component.


4️⃣ children?: RouteRecordStringComponent<T>[]

Пример:

{
  path: '/admin',
  component: 'BasicLayout',
  children: [
    {
      path: 'users',
      component: 'Users'
    }
  ]
}

5️⃣ component: T

Главное отличие:

component: string

вместо:

component: Component

Это позволяет:


Зачем это нужно (практика)

📡 Сервер отдаёт маршруты

{
  "path": "/users",
  "component": "Users",
  "meta": { "title": "Пользователи" }
}

🧠 На фронте

const route: RouteRecordStringComponent = data

🔄 Потом:

route.component = pageMap[`../views/${route.component}.vue`]

Чем отличается от RouteRecordRaw

Поле RouteRecordRaw RouteRecordStringComponent
component Vue компонент string
children RouteRecordRaw[] RouteRecordStringComponent[]
Использование Обычные роуты Динамические / backend-routes

Коротко

Это тип маршрута Vue Router,
где componentне компонент, а строка,
чтобы позже заменить её на реальный компонент.

Архитектура

Что означает: interface GenerateMenuAndRoutesOptions { fetchMenuListAsync?: () => ...

interface GenerateMenuAndRoutesOptions {
  fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;
  forbiddenComponent?: RouteRecordRaw['component'];
  layoutMap?: ComponentRecordType;
  pageMap?: ComponentRecordType;
  roles?: string[];
  router: Router;
  routes: RouteRecordRaw[];
}

Это TypeScript-интерфейс, который описывает объект с настройками для функции (скорее всего generateAccessible).
Разберём что означает КАЖДАЯ строка и зачем это нужно 👇


Общий смысл

interface GenerateMenuAndRoutesOptions { ... }

👉 Это контракт:


Разбор по полям


🔹 fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;

📌 То есть:

fetchMenuListAsync: async () => {
  return getAllMenusApi()
}

👉 callback, который:


🔹 forbiddenComponent?: RouteRecordRaw['component'];

Это:

«возьми тип поля component из RouteRecordRaw»

Фактически:

Component | (() => Promise<Component>)

Используется как:


🔹 layoutMap?: ComponentRecordType;

Карта layout-компонентов:

{
  BasicLayout: BasicLayout,
  IFrameView: IFrameView,
}

Используется, чтобы:


🔹 pageMap?: ComponentRecordType;

Карта страниц:

import.meta.glob('../views/**/*.vue')

Используется, чтобы:


🔹 roles?: string[];

Массив ролей пользователя:

['admin', 'editor']

Используется для:


🔹 router: Router; ✅ (обязательное)

Экземпляр Vue Router:

const router = createRouter(...)

Используется для:


🔹 routes: RouteRecordRaw[]; ✅ (обязательное)

Массив базовых маршрутов:

[
  { path: '/login', component: Login },
  { path: '/404', component: NotFound }
]

Это:


Обязательные vs необязательные

Поле Обязательное
router
routes
fetchMenuListAsync
layoutMap
pageMap
roles
forbiddenComponent

Как это используется на практике

generateAccessible(mode, {
  router,
  routes,
  pageMap,
  layoutMap,
  fetchMenuListAsync,
})

TypeScript:


Коротко

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

Архитектура

Что означает: options.routes = cloneDeep(options.routes);

Эта строка означает:
👉 создаётся глубокая копия массива маршрутов, и она записывается обратно в options.routes.

Строка целиком

options.routes = cloneDeep(options.routes);

Что такое cloneDeep

cloneDeep — обычно функция из lodash:

import { cloneDeep } from 'lodash-es';

Она:


Почему нельзя просто так

❌ Поверхностная копия

const copy = [...options.routes];

Проблема:

copy[0].meta.title = 'New';
// изменится и в original ❌

cloneDeep

const copy = cloneDeep(options.routes);

Теперь:

copy[0].meta.title = 'New';
// original НЕ изменится ✅

Что реально происходит пошагово

1️⃣ Берётся options.routes
2️⃣ Создаётся полный клон всей структуры маршрутов
3️⃣ options.routes теперь указывает на новую копию
4️⃣ Оригинальные маршруты остаются нетронутыми


Зачем это делают (очень важно)

В коде генерации роутов обычно:

❗ Если менять оригинал:


Типичный кейс из твоего контекста

options.routes = cloneDeep(options.routes);

// дальше
options.routes.forEach(route => {
  route.children?.push(dynamicRoute)
})

👉 изменения безопасны
👉 исходные routes не портятся


Аналогия 🧠


Коротко

Эта строка защищает исходные маршруты,
создавая их полную независимую копию перед изменениями.