Реактивность: ref vs useState
React useState
const [count, setCount] = useState(0);
setCount(5);
setCount(c => c + 1);
Состояние + сеттер. Обновление явное.
Vue ref
const count = ref(0);
count.value = 5;
count.value++;
Одно значение. Обновление через .value.
Ключевое отличие
React: setCount(newValue) — вызвать функцию.
Vue: count.value = newValue — присвоить значение.
Vue использует Proxy — следит за изменениями автоматически.
Реактивность объектов
React:
const [user, setUser] = useState({ name: 'Иван' });
setUser({ ...user, name: 'Петр' }); // новый объект
Vue:
const user = reactive({ name: 'Иван' });
user.name = 'Петр'; // просто присвоить
React требует иммутабельность. Vue — мутации.
Массивы
React:
setItems([...items, newItem]);
setItems(items.filter(i => i.id !== id));
Vue:
items.value.push(newItem);
items.value = items.value.filter(i => i.id !== id);
Вложенные объекты
React:
setUser({
...user,
address: { ...user.address, city: 'СПб' },
});
Vue:
user.address.city = 'СПб';
Vue проще на глубоких структурах.
Батчинг
React 18:
setCount(c => c + 1); // батчинг
setName('Иван'); // батчинг
// Один рендер
Vue:
count.value++;
name.value = 'Иван';
// Один рендер (автоматически)
Vue всегда батчит. React — в 18+.
Ссылки на DOM
React:
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />
inputRef.current?.focus();
Vue:
<script setup lang="ts">
import { ref } from 'vue';
const inputRef = ref<HTMLInputElement | null>(null);
function focus() {
inputRef.value?.focus();
}
</script>
<template>
<input ref="inputRef">
</template>
Почти одинаково.
useRef vs ref
React useRef — неизменяемый объект со свойством .current.
const id = useRef(1);
id.current = 2; // не вызывает рендер
Vue ref — реактивное значение.
const id = ref(1);
id.value = 2; // реактивно
Не путать! Vue ref = React useState.
Вычисляемые значения
React:
const doubled = useMemo(() => count * 2, [count]);
Vue:
const doubled = computed(() => count.value * 2);
Vue не надо указывать зависимости — определяет автоматически.
Формы
React — контролируемый:
<input
value={name}
onChange={e => setName(e.target.value)}
/>
Vue — v-model:
<input v-model="name">
Vue сокращённо — один сахар для value + onChange.
Что общего
- Реактивные значения
- Триггерят ререндер
- Локальны для компонента
- Работают с DOM через ref
Плюсы Vue
- Меньше кода
- Мутации проще иммутабельности
- Автоматический трекинг зависимостей
- Проще вложенные структуры
Плюсы React
- Явный контроль
- Легче отследить обновления
- Иммутабельность = предсказуемость
- Больше инструментов
Полный пример
React:
export function Counter() {
const [count, setCount] = useState(0);
const [history, setHistory] = useState<number[]>([]);
const increment = () => {
const next = count + 1;
setCount(next);
setHistory([...history, next]);
};
return (
<div>
<p>{count}</p>
<p>{history.join(', ')}</p>
<button onClick={increment}>+1</button>
</div>
);
}
Vue:
<script setup lang="ts">
import { ref, reactive } from 'vue';
const state = reactive({
count: 0,
history: [] as number[],
});
function increment() {
state.count++;
state.history.push(state.count);
}
</script>
<template>
<div>
<p>{{ state.count }}</p>
<p>{{ state.history.join(', ') }}</p>
<button @click="increment">+1</button>
</div>
</template>
Итог
- Vue
ref= ReactuseState - React — сеттер, Vue — прямое присвоение
- React — иммутабельность, Vue — мутации
- Vue
computed= ReactuseMemo, но без массива зависимостей
Дальше — шаблоны vs JSX.