Урок 02

Реактивность: 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 = React useState
  • React — сеттер, Vue — прямое присвоение
  • React — иммутабельность, Vue — мутации
  • Vue computed = React useMemo, но без массива зависимостей

Дальше — шаблоны vs JSX.