28. Vue 3 vs React: сравнение
⚔️ Урок 30: Vue 3 vs React — сравнение
Заголовок раздела «⚔️ Урок 30: Vue 3 vs React — сравнение»Vue 3 и React — два самых популярных фреймворка для создания современных веб-приложений. Оба мощные, оба имеют огромное комьюнити, оба используются в продакшне крупнейших компаний. Но у каждого своя философия, свои сильные стороны и случаи применения. Давай разберём их честно и без фанатизма. 🎯
🧠 Ментальная модель
Заголовок раздела «🧠 Ментальная модель»Vue 3: «Шаблон — это правда»
Vue придерживается классического подхода: есть шаблон HTML, есть логика в <script>, есть стили в <style>. Разделение ответственности в одном файле.
<!-- Всё понятно с первого взгляда --><template> <button :class="{ active: isActive }" @click="toggle"> {{ isActive ? 'Активно' : 'Неактивно' }} </button></template>
<script setup lang="ts">const isActive = ref(false);const toggle = () => { isActive.value = !isActive.value; };</script>
<style scoped>.active { background: #42b883; }</style>React: «JavaScript — это правда»
React идёт от обратного: всё — JavaScript. JSX, стили, логика — всё в одном языке.
// Всё — просто функции и JavaScriptfunction Button() { const [isActive, setIsActive] = useState(false);
return ( <button className={isActive ? 'active' : ''} onClick={() => setIsActive(a => !a)} style={{ background: isActive ? '#61dafb' : undefined }} > {isActive ? 'Активно' : 'Неактивно'} </button> );}⚡ Реактивность vs useState
Заголовок раздела «⚡ Реактивность vs useState»Это самое принципиальное различие между фреймворками.
Vue 3: автоматическая реактивность
// Vue отслеживает зависимости автоматическиconst count = ref(0);const doubled = computed(() => count.value * 2);
// Просто присваивай — Vue знает, что обновитьcount.value++;<template> <!-- Автоматически обновляется при изменении count --> <p>{{ count }} x2 = {{ doubled }}</p>
<!-- Вложенные объекты тоже реактивны --> <p>{{ user.profile.name }}</p></template>
<script setup>const count = ref(0);const user = reactive({ profile: { name: 'Иван', age: 25 }});
// Прямая мутация — работает!user.profile.name = 'Пётр';user.profile.age++;</script>React: явный setState
// React требует явного обновления состоянияconst [count, setCount] = useState(0);
// Нельзя мутировать напрямую — нужен settersetCount(c => c + 1);
// Объекты — обязательно создавать новыйconst [user, setUser] = useState({ name: 'Иван', age: 25 });// ❌ user.name = 'Пётр'; — не сработает// ✅ Правильно:setUser(u => ({ ...u, name: 'Пётр' }));📄 Шаблоны vs JSX
Заголовок раздела «📄 Шаблоны vs JSX»Vue 3: HTML-шаблоны с директивами
<template> <!-- Условия --> <div v-if="isLoggedIn">Привет, {{ user.name }}!</div> <div v-else>Войдите в систему</div>
<!-- Списки --> <ul> <li v-for="item in items" :key="item.id"> {{ item.name }} </li> </ul>
<!-- Форма с двусторонней привязкой --> <input v-model="searchQuery" placeholder="Поиск..." />
<!-- Событие с модификатором --> <form @submit.prevent="handleSubmit"> <button type="submit" :disabled="isLoading"> {{ isLoading ? 'Загрузка...' : 'Отправить' }} </button> </form></template>React: JSX — JavaScript с синтаксисом HTML
function MyComponent() { const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(false);
return ( <> {/* Условия */} {isLoggedIn ? ( <div>Привет, {user.name}!</div> ) : ( <div>Войдите в систему</div> )}
{/* Списки */} <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul>
{/* Нет v-model — вручную */} <input value={searchQuery} onChange={e => setSearchQuery(e.target.value)} placeholder="Поиск..." />
{/* Нет .prevent — вручную */} <form onSubmit={e => { e.preventDefault(); handleSubmit(); }}> <button type="submit" disabled={isLoading}> {isLoading ? 'Загрузка...' : 'Отправить'} </button> </form> </> );}🎣 Директивы vs Хуки
Заголовок раздела «🎣 Директивы vs Хуки»Vue 3: Composables + встроенные директивы
<script setup>// Composable — похожи на хуки Reactconst { data, isLoading } = useFetch('/api/posts');const { x, y } = useMouse();const isDark = usePreferredDark();
// Lifecycle — явные, понятные именаonMounted(() => console.log('DOM готов'));onUnmounted(() => console.log('Очистка'));onBeforeUpdate(() => console.log('Перед обновлением'));</script>
<template> <!-- Директивы — встроенная логика в шаблоне --> <div v-show="!isLoading"> <img v-lazy="imageUrl" /> <div v-tooltip="'Подсказка'" /> <input v-focus /> </div></template>React: Хуки
// Хуки Reactfunction MyComponent() { const { data, isLoading } = useFetch('/api/posts'); const { x, y } = useMouse();
// Lifecycle через useEffect useEffect(() => { console.log('DOM готов'); return () => console.log('Очистка'); }, []);
// Нет v-show — вручную // Нет v-lazy — библиотека или кастомный компонент // Нет v-tooltip — пропс или HOC
return ( <div style={{ display: isLoading ? 'none' : 'block' }}> {/* ... */} </div> );}🔢 computed vs useMemo / useCallback
Заголовок раздела «🔢 computed vs useMemo / useCallback»Vue 3: computed — магически простой
const firstName = ref('Иван');const lastName = ref('Петров');
// Автоматически пересчитывается только при изменении зависимостейconst fullName = computed(() => \`\${firstName.value} \${lastName.value}\`);
// Computed с getter и setterconst email = computed({ get: () => store.user.email.toLowerCase(), set: (val) => store.updateEmail(val),});React: useMemo и useCallback — явные зависимости
const firstName = 'Иван';const lastName = 'Петров';
// Нужно явно указывать зависимостиconst fullName = useMemo( () => \`\${firstName} \${lastName}\`, [firstName, lastName] // ← легко забыть или указать лишнее);
// Для функций — useCallbackconst handleClick = useCallback( (id: number) => { doSomethingWith(id, userId); }, [userId] // ← если забыть userId — баг с устаревшим замыканием);Главное различие: Vue отслеживает зависимости автоматически во время выполнения, React требует явного указания зависимостей.
🏪 Pinia vs Zustand / Redux
Заголовок раздела «🏪 Pinia vs Zustand / Redux»Pinia (Vue):
// Минималистично и TypeScript-friendlyexport const useCartStore = defineStore('cart', () => { const items = ref<CartItem[]>([]); const total = computed(() => items.value.reduce((s, i) => s + i.price, 0));
function addItem(item: CartItem) { items.value.push(item); }
return { items, total, addItem };});
// В компонентеconst cart = useCartStore();cart.addItem({ id: 1, name: 'Товар', price: 100 });Zustand (React):
// Похоже на Pinia, но для Reactconst useCartStore = create<CartStore>((set, get) => ({ items: [], total: 0,
addItem: (item) => set(state => ({ items: [...state.items, item], total: state.total + item.price, })),}));
// В компонентеconst { items, addItem } = useCartStore();addItem({ id: 1, name: 'Товар', price: 100 });Redux Toolkit (React):
// Более многословно, но предсказуемоconst cartSlice = createSlice({ name: 'cart', initialState: { items: [] as CartItem[], total: 0 }, reducers: { addItem(state, action: PayloadAction<CartItem>) { state.items.push(action.payload); state.total += action.payload.price; }, removeItem(state, action: PayloadAction<number>) { state.items = state.items.filter(i => i.id !== action.payload); }, },});📦 Экосистема
Заголовок раздела «📦 Экосистема»Vue 3:
- 📦 Vite — невероятно быстрая сборка (создана автором Vue)
- 🔀 Vue Router — официальный роутер
- 🏪 Pinia — официальный стейт-менеджер
- 🌐 Nuxt 3 — SSR фреймворк
- 🧪 Vitest — тесты (от команды Vite)
- 💅 VueUse — 200+ composables
React:
- 🔀 React Router / TanStack Router — роутинг
- 🏪 Zustand / Redux / Jotai / Recoil — состояние
- 🌐 Next.js / Remix — SSR/SSG
- 🧪 Jest / Vitest — тесты
- 💅 React Query / SWR — серверный стейт
- 🎨 Styled-Components / Tailwind — стили
📊 Кривая обучения
Заголовок раздела «📊 Кривая обучения»Vue 3:Неделя 1: Шаблоны, ref, reactive, v-for, v-if ← ЛЕГКОНеделя 2: Компоненты, props, emits ← ЛЕГКОМесяц 1: Composables, Router, Pinia ← СРЕДНЕМесяц 2+: SSR, TypeScript, оптимизация ← СЛОЖНО
React:Неделя 1: JSX, useState, props ← СРЕДНЕНеделя 2: useEffect, useCallback, useMemo ← СЛОЖНО (хуки!)Месяц 1: Состояние, роутинг, паттерны ← СЛОЖНОМесяц 2+: SSR, оптимизация, concurrency ← ОЧЕНЬ СЛОЖНО🎯 Когда выбрать Vue 3?
Заголовок раздела «🎯 Когда выбрать Vue 3?»✅ Команда пришла из традиционного HTML/CSS/JS
✅ Нужна быстрая разработка без глубокого JS background
✅ Проект типа CMS, корпоративный портал, dashboard
✅ SEO важен → Nuxt 3 из коробки
✅ Предпочитаете чёткое разделение HTML/JS/CSS
✅ Работаете с дизайнерами, которые пишут шаблоны
🎯 Когда выбрать React?
Заголовок раздела «🎯 Когда выбрать React?»✅ Команда имеет сильный JavaScript/TypeScript background
✅ Огромная экосистема и максимум npm-пакетов
✅ Нужна максимальная гибкость архитектуры
✅ Мобильная разработка → React Native
✅ Сложные интерактивные UI, анимации
✅ Много разработчиков на рынке труда
📊 Итоговое сравнение
Заголовок раздела «📊 Итоговое сравнение»| Аспект | Vue 3 | React |
|---|---|---|
| Кривая обучения | Пологая | Крутая |
| Шаблоны | HTML + директивы | JSX |
| Реактивность | Автоматическая | Явная |
| Производительность | Отличная | Отличная |
| TypeScript | Отличная поддержка | Отличная поддержка |
| Экосистема | Богатая | Огромная |
| SSR | Nuxt 3 | Next.js / Remix |
| Стейт | Pinia | Zustand / Redux |
| Гибкость | Высокая | Максимальная |
| Рынок труда | Средний | Огромный |
Главный вывод: Оба фреймворка отлично справляются с задачами. Vue 3 проще в изучении и разработке. React гибче и имеет больший рынок труда. Выбирай тот, который лучше подходит твоей команде и задаче. Знание обоих — бесценное преимущество! 🚀