Vue.jsMiddleExperience

Какие архитектурные решения Vue.js навязывает вокруг rendering, state, routing, styling, data loading или deployment?

Vue навязывает Proxy-реактивность с batch-обновлениями, SFC-компилятор с static hoisting и patch flags, Vue Router 4 с async guards, Pinia для state, scoped CSS в компонентах. Data loading и deployment архитектура остаётся на выбор разработчика (Vite, Nuxt/Nitro).

Rendering: виртуальный DOM и компилятор

Vue 3 компилирует .vue-шаблоны в функции рендера при сборке (AOT через @vue/compiler-dom). Компилятор применяет статический подъём (static hoisting) и патч-флаги (patch flags) — числа в VNode, указывающие, какие именно свойства могут измениться. Во время обновления Vue сравнивает только «помеченные» VNode, что делает diff в разы быстрее, чем наивный обход дерева.

Рантайм рендерер (@vue/runtime-dom) отделён от ядра (@vue/runtime-core): это позволяет собирать рендереры для Weex, NativeScript или тестовых окружений без изменения бизнес-логики.

State: реактивная система Proxy

Vue 3 строит реактивность на Proxy (reactive(), ref(), computed(), watchEffect()). При чтении свойства активный эффект регистрируется как зависимость (track); при записи все зависимые эффекты ставятся в очередь (trigger → scheduler). Обновления DOM асинхронны и батчируются через Promise.resolve(); nextTick() даёт доступ к DOM после применения всех изменений очереди.

import { ref, computed, watchEffect, nextTick } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)

watchEffect(() => console.log('double:', double.value))

count.value++
await nextTick() // DOM обновлён

Routing: Vue Router 4

Vue Router 4 — официальный роутер; интегрируется через app.use(router). Архитектурное решение: навигационные хуки (beforeEach, beforeResolve, afterEach) выполняются в строгом порядке и асинхронны. Lazy-loaded routes через динамический import() — стандартный способ code splitting:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('./views/Home.vue') },
    {
      path: '/admin',
      component: () => import('./views/Admin.vue'),
      meta: { requiresAuth: true },
    },
  ],
})

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    return { name: 'login' }
  }
})

State management: Pinia

Pinia — официальный менеджер состояния для Vue 3. Стор — это просто composable, доступный глобально; поддерживает DevTools, SSR, плагины и TypeScript без дополнительной конфигурации. В отличие от Vuex 4 нет mutations — только actions и getters (computed).

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const total = computed(() => items.value.reduce((s, i) => s + i.price, 0))

  async function addItem(id: string) {
    const item = await fetchItem(id)
    items.value.push(item)
  }

  return { items, total, addItem }
})

Styling: Scoped CSS и CSS Modules

Vue SFC поддерживает <style scoped> — компилятор добавляет data-атрибут к элементам и селекторам, изолируя стили компонента. <style module> даёт CSS Modules с доступом через $style.className. v-bind() в CSS позволяет реактивно использовать значения из script setup как CSS custom properties.

Data loading: нет встроенного решения

Vue не навязывает способ загрузки данных — это архитектурное решение разработчика. Варианты:

  • onMounted + fetch / axios — базовый подход.
  • TanStack Query (vue-query) — кеширование, фоновая синхронизация, staleTime, refetchOnWindowFocus.
  • Nuxt useFetch / useAsyncData — SSR-совместимая загрузка с дедупликацией и гидрацией.
  • Pinia store с async actions — для глобального состояния с кешированием.

Deployment: Vite и Nuxt

Стандартный билд — vite build, результат — статические файлы в dist/. Для SSR используется Nuxt 3 (Node.js, Bun, edge-рантаймы через Nitro — Cloudflare Workers, Vercel Edge). vite.config.ts управляет code splitting, tree shaking, target браузеров через @vitejs/plugin-vue.

Подводные камни

  • Мутация реактивного объекта напрямую (state.list[0] = x) работает через Proxy, но delete state.key надо заменять на delete reactive(state).key или пересоздавать объект.
  • Потеря реактивности при деструктуризации: const { count } = store разрывает связь — используйте storeToRefs(store).
  • Глубокая реактивность больших объектов через reactive() может быть медленной — используйте shallowReactive() / shallowRef() для таблиц и списков.
  • <style scoped> не пробрасывается в дочерние компоненты — для переопределения используйте :deep() селектор.
  • watchEffect запускается синхронно при регистрации (eager) и в фазе pre-flush по умолчанию — для доступа к обновлённому DOM используйте { flush: 'post' }.
  • Vue Router 4 навигация асинхронна — router.push() возвращает Promise; ошибки типа NavigationDuplicated надо обрабатывать явно.
  • Pinia не поддерживает time-travel debugging из коробки (в отличие от Vuex с плагином); для этого нужен pinia-plugin-persistedstate + ручная история.
  • Nuxt useAsyncData кеширует по ключу — одинаковые ключи в разных компонентах дадут один запрос, что может быть неожиданным поведением.

What hurts your answer

  • Знать термины Vue.js, но не понимать связи между абстракциями
  • Объяснять поведение через отдельные примеры вместо причинной модели
  • Не связывать mental model с диагностикой ошибок

What they're listening for

  • Понимает ключевые абстракции Vue.js
  • Может предсказывать поведение системы через mental model
  • Связывает модель с debugging и production decisions

Related topics