29. Svelte vs React vs Vue
⚔️ Svelte vs React vs Vue: Честное сравнение
Заголовок раздела «⚔️ Svelte vs React vs Vue: Честное сравнение»Привет! 👋 Выбор фреймворка — это одно из самых спорных решений в мире фронтенда. У каждого свои фанаты, у каждого свои недостатки. Давай разберём это честно, без фанатизма, с конкретными примерами кода.
Представь три ресторана: React — это McDonald’s (везде есть, всегда предсказуемо, огромное меню), Vue — это уютное кафе (приятная атмосфера, доступно), а Svelte — это модный foodtruck с авторской кухней (маленький, но потрясающий). Зависит от того, что ты ищешь!
📊 Таблица сравнения: кто чем хорош
Заголовок раздела «📊 Таблица сравнения: кто чем хорош»| Характеристика | Svelte 5 | React 19 | Vue 3 |
|---|---|---|---|
| Подход | Компилятор | Runtime (VDOM) | Runtime (VDOM) |
| Bundle size (app) | ~10-30 KB | ~130-200 KB | ~50-80 KB |
| Performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Кривая обучения | Пологая | Крутая | Средняя |
| TypeScript DX | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Реактивность | Сигналы | Хуки | Options/Composition |
| SSR фреймворк | SvelteKit | Next.js | Nuxt.js |
| Экосистема | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Работа на рынке | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Boilerplate | Минимальный | Средний | Минимальный |
| Тестирование | Vitest + PW | Jest + RTL | Vitest + VTU |
| Сообщество | Растущее | Огромное | Большое |
| Мобайл | SvelteNative | React Native | Capacitor/Ionic |
| Веб-компоненты | ✅ Встроено | Через библиотеки | Встроено |
🔄 Реактивность: три разных подхода
Заголовок раздела «🔄 Реактивность: три разных подхода»Одна задача — три решения: счётчик с derived значением
<!-- Svelte 5 --><script lang="ts"> let count = $state(0); let doubled = $derived(count * 2);
function increment() { count++; }</script>
<p>Count: {count}</p><p>Doubled: {doubled}</p><button onclick={increment}>+1</button>// React 18+import { useState, useMemo } from 'react';
function Counter() { const [count, setCount] = useState(0); const doubled = useMemo(() => count * 2, [count]);
return ( <> <p>Count: {count}</p> <p>Doubled: {doubled}</p> <button onClick={() => setCount(c => c + 1)}>+1</button> </> );}<!-- Vue 3 Composition API --><script setup lang="ts"> import { ref, computed } from 'vue';
const count = ref(0); const doubled = computed(() => count.value * 2);
function increment() { count.value++; }</script>
<template> <p>Count: {{ count }}</p> <p>Doubled: {{ doubled }}</p> <button @click="increment">+1</button></template>📋 Форма с валидацией: сравнение verbosity
Заголовок раздела «📋 Форма с валидацией: сравнение verbosity»<!-- Svelte 5 — минимальный boilerplate --><script lang="ts"> let email = $state(''); let password = $state('');
let emailError = $derived( !email ? '' : !email.includes('@') ? 'Неверный email' : '' );
let passwordError = $derived( !password ? '' : password.length < 8 ? 'Минимум 8 символов' : '' );
let isValid = $derived(!emailError && !passwordError && !!email && !!password);
async function handleSubmit() { if (!isValid) return; await login(email, password); }</script>
<form onsubmit={handleSubmit}> <input bind:value={email} type="email" placeholder="Email" /> {#if emailError}<span>{emailError}</span>{/if}
<input bind:value={password} type="password" placeholder="Password" /> {#if passwordError}<span>{passwordError}</span>{/if}
<button type="submit" disabled={!isValid}>Войти</button></form>// React — больше кода, хукиimport { useState, useMemo, FormEvent } from 'react';
function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [touched, setTouched] = useState({ email: false, password: false });
const emailError = useMemo(() => { if (!email) return ''; if (!email.includes('@')) return 'Неверный email'; return ''; }, [email]);
const passwordError = useMemo(() => { if (!password) return ''; if (password.length < 8) return 'Минимум 8 символов'; return ''; }, [password]);
const isValid = !emailError && !passwordError && !!email && !!password;
async function handleSubmit(e: FormEvent) { e.preventDefault(); if (!isValid) return; await login(email, password); }
return ( <form onSubmit={handleSubmit}> <input value={email} onChange={(e) => setEmail(e.target.value)} onBlur={() => setTouched(t => ({ ...t, email: true }))} type="email" placeholder="Email" /> {touched.email && emailError && <span>{emailError}</span>}
<input value={password} onChange={(e) => setPassword(e.target.value)} onBlur={() => setTouched(t => ({ ...t, password: true }))} type="password" placeholder="Password" /> {touched.password && passwordError && <span>{passwordError}</span>}
<button type="submit" disabled={!isValid}>Войти</button> </form> );}🔄 Глобальный стейт: сравнение
Заголовок раздела «🔄 Глобальный стейт: сравнение»<!-- Svelte: stores или reactive classes --><script lang="ts"> import { writable, derived } from 'svelte/store';
export const user = writable<User | null>(null); export const cart = writable<CartItem[]>([]); export const cartTotal = derived(cart, $cart => $cart.reduce((sum, item) => sum + item.price * item.qty, 0) );</script>
<!-- В компоненте: --><script lang="ts"> import { cart, cartTotal } from '$lib/stores'; import { user } from '$lib/stores';</script>
<p>{$user?.name}</p><p>Итого: {$cartTotal} ₽</p><button onclick={() => cart.update(c => [...c, newItem])}> Добавить</button>// React: обычно Zustand или Redux Toolkitimport { create } from 'zustand';
interface CartStore { cart: CartItem[]; user: User | null; addItem: (item: CartItem) => void; setUser: (user: User | null) => void; total: () => number;}
export const useStore = create<CartStore>((set, get) => ({ cart: [], user: null, addItem: (item) => set((s) => ({ cart: [...s.cart, item] })), setUser: (user) => set({ user }), total: () => get().cart.reduce((sum, item) => sum + item.price * item.qty, 0),}));
// В компоненте:function CartTotal() { const total = useStore((s) => s.total()); return <p>Итого: {total} ₽</p>;}⚡ Эффекты и lifecycle: сравнение
Заголовок раздела «⚡ Эффекты и lifecycle: сравнение»<!-- Svelte 5 --><script lang="ts"> // onMount = $effect (первый запуск после монтирования) $effect(() => { const ws = new WebSocket('wss://example.com'); ws.onmessage = handleMessage;
return () => ws.close(); // cleanup = return функция });
// $effect.pre = beforeUpdate $effect.pre(() => { // до обновления DOM });</script>// Reactimport { useEffect, useLayoutEffect, useRef } from 'react';
function Component() { const wsRef = useRef<WebSocket>();
// Аналог $effect: useEffect(() => { const ws = new WebSocket('wss://example.com'); ws.onmessage = handleMessage; wsRef.current = ws;
return () => ws.close(); // cleanup }, []); // [] = только при монтировании
// Аналог $effect.pre: useLayoutEffect(() => { // до DOM paint }, []);
return null;}🛣️ Роутинг: сравнение
Заголовок раздела «🛣️ Роутинг: сравнение»SvelteKit: src/routes/+page.svelte → / src/routes/blog/[slug]/+page.svelte → /blog/:slug +page.ts / +page.server.ts → Load функции
Next.js App Router: app/page.tsx → / app/blog/[slug]/page.tsx → /blog/:slug React Server Components → Данные в компоненте
Nuxt 3: pages/index.vue → / pages/blog/[slug].vue → /blog/:slug useFetch() / useAsyncData() → Данные💼 Управление состоянием формы: сравнение
Заголовок раздела «💼 Управление состоянием формы: сравнение»bind: vs onChange: тащиться или нет?
<!-- Svelte — двустороннее связывание встроено --><input bind:value={name} /><!-- Эквивалентно: value={name} oninput={(e) => name = e.target.value} -->
<!-- С трансформацией: --><input value={name.toUpperCase()} oninput={(e) => name = e.currentTarget.value.toLowerCase()}/>// React — всегда явный onChange (controlled component)<input value={name} onChange={(e) => setName(e.target.value)}/><!-- Vue — v-model как в Svelte --><input v-model="name" /><!-- Эквивалентно: :value="name" @input="name = $event.target.value" -->🤔 Когда выбирать что
Заголовок раздела «🤔 Когда выбирать что»Выбери React если:
Заголовок раздела «Выбери React если:»✅ Команда уже знает React✅ Нужны React Native мобильные приложения✅ Важен размер рынка труда✅ Нужны специфические React библиотеки (Framer Motion, React Query)✅ Большое enterprise приложение с 50+ разработчиками✅ Нужна максимально документированная экосистемаВыбери Vue если:
Заголовок раздела «Выбери Vue если:»✅ Нравится Options API (близко к HTML)✅ Команда знает Vue / Angular✅ Нужно плавно мигрировать с Angular✅ Проект в Азии (Vue очень популярен там)✅ Нужен Nuxt.js с его экосистемойВыбери Svelte если:
Заголовок раздела «Выбери Svelte если:»✅ Новый проект без legacy ограничений✅ Производительность критически важна✅ Команда хочет изучить что-то новое✅ Небольшой или средний проект✅ Cloudflare Workers или edge computing✅ Нужен маленький bundle (медленный интернет, мобайл)✅ Хочешь кайфовать от разработки🔀 Миграционные пути
Заголовок раздела «🔀 Миграционные пути»React → Svelte:
// Постепенная миграция возможна:// 1. Создай новые фичи в Svelte// 2. Используй Svelte компоненты как Web Components в React// 3. Переписывай компонент за компонентом
// Svelte как Web Component:// <svelte:options customElement="my-button" />// Потом в React: <my-button onClick={...} />Vue → Svelte:
Vue Options API → Svelte 4 (похожий синтаксис)Vue Composition API → Svelte 5 ($state/$derived/$effect)Vuex/Pinia → Svelte Stores / Reactive classesNuxt → SvelteKitVue Router → SvelteKit routing📈 Тренды и экосистема
Заголовок раздела «📈 Тренды и экосистема»GitHub Stars (2024): React 220k+ (Meta backed) Vue 207k+ (Community driven) Svelte 77k+ (Vercel backed)
npm downloads в неделю: React ~25M Vue ~4M Svelte ~800K
Job listings: React ~70% Vue ~15% Svelte ~5%
Developer satisfaction: Svelte ⭐⭐⭐⭐⭐ (State of JS 2023: #1) Vue ⭐⭐⭐⭐ React ⭐⭐⭐⭐