Перейти к содержимому

28. 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, стили, логика — всё в одном языке.

// Всё — просто функции и JavaScript
function Button() {
const [isActive, setIsActive] = useState(false);
return (
<button
className={isActive ? 'active' : ''}
onClick={() => setIsActive(a => !a)}
style={{ background: isActive ? '#61dafb' : undefined }}
>
{isActive ? 'Активно' : 'Неактивно'}
</button>
);
}

Это самое принципиальное различие между фреймворками.

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);
// Нельзя мутировать напрямую — нужен setter
setCount(c => c + 1);
// Объекты — обязательно создавать новый
const [user, setUser] = useState({ name: 'Иван', age: 25 });
// ❌ user.name = 'Пётр'; — не сработает
// ✅ Правильно:
setUser(u => ({ ...u, name: 'Пётр' }));

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>
</>
);
}

Vue 3: Composables + встроенные директивы

<script setup>
// Composable — похожи на хуки React
const { 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: Хуки

// Хуки React
function 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>
);
}

Vue 3: computed — магически простой

const firstName = ref('Иван');
const lastName = ref('Петров');
// Автоматически пересчитывается только при изменении зависимостей
const fullName = computed(() => \`\${firstName.value} \${lastName.value}\`);
// Computed с getter и setter
const 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] // ← легко забыть или указать лишнее
);
// Для функций — useCallback
const handleClick = useCallback(
(id: number) => {
doSomethingWith(id, userId);
},
[userId] // ← если забыть userId — баг с устаревшим замыканием
);

Главное различие: Vue отслеживает зависимости автоматически во время выполнения, React требует явного указания зависимостей.


Pinia (Vue):

// Минималистично и TypeScript-friendly
export 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, но для React
const 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 ← ОЧЕНЬ СЛОЖНО

✅ Команда пришла из традиционного HTML/CSS/JS

✅ Нужна быстрая разработка без глубокого JS background

✅ Проект типа CMS, корпоративный портал, dashboard

✅ SEO важен → Nuxt 3 из коробки

✅ Предпочитаете чёткое разделение HTML/JS/CSS

✅ Работаете с дизайнерами, которые пишут шаблоны


✅ Команда имеет сильный JavaScript/TypeScript background

✅ Огромная экосистема и максимум npm-пакетов

✅ Нужна максимальная гибкость архитектуры

✅ Мобильная разработка → React Native

✅ Сложные интерактивные UI, анимации

✅ Много разработчиков на рынке труда


АспектVue 3React
Кривая обученияПологаяКрутая
ШаблоныHTML + директивыJSX
РеактивностьАвтоматическаяЯвная
ПроизводительностьОтличнаяОтличная
TypeScriptОтличная поддержкаОтличная поддержка
ЭкосистемаБогатаяОгромная
SSRNuxt 3Next.js / Remix
СтейтPiniaZustand / Redux
ГибкостьВысокаяМаксимальная
Рынок трудаСреднийОгромный

Главный вывод: Оба фреймворка отлично справляются с задачами. Vue 3 проще в изучении и разработке. React гибче и имеет больший рынок труда. Выбирай тот, который лучше подходит твоей команде и задаче. Знание обоих — бесценное преимущество! 🚀