Какие архитектурные решения 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