Урок 02

Реактивность: ref и reactive

Что такое реактивность

Реактивность — Vue следит за данными и обновляет UI при изменениях.

Основа Vue. Без неё — обычный HTML.

ref — реактивное значение

ref() — обёртка для примитива или объекта.

import { ref } from 'vue';

const count = ref(0);
const name = ref('Иван');
const user = ref({ name: 'Иван', age: 25 });
const items = ref([]);

Доступ в JS:

count.value;         // 0
count.value = 5;

name.value;          // 'Иван'

user.value.name;     // 'Иван'
user.value.age = 30;

items.value.push('item');

Доступ в шаблоне — без .value:

<p>{{ count }}</p>
<p>{{ name }}</p>
<p>{{ user.name }}</p>

Правило:

  • В JS — .value
  • В шаблоне — без .value

reactive — реактивный объект

reactive() — для объектов и массивов.

import { reactive } from 'vue';

const user = reactive({
    name: 'Иван',
    age: 25,
    address: {
        city: 'Москва',
    },
});

user.name;               // 'Иван' — без .value
user.name = 'Петр';
user.address.city;       // вложенные тоже реактивны

ref vs reactive

refreactive
Длялюбыхобъектов
Доступ.valueнапрямую
Переприсвоитьданет
Деструктуризациядатеряет реактивность
В шаблонебез .valueбез .value

Правило: используй ref — универсальнее.

Переприсвоение

ref:

const user = ref({ name: 'Иван' });
user.value = { name: 'Петр' };   // ✅ работает

reactive:

let user = reactive({ name: 'Иван' });
user = { name: 'Петр' };         // ❌ не работает — теряется реактивность

Деструктуризация

ref:

const user = ref({ name: 'Иван', age: 25 });
const { name, age } = user.value;   // ❌ теряется реактивность

Правильно — toRefs:

import { toRefs } from 'vue';

const user = reactive({ name: 'Иван', age: 25 });
const { name, age } = toRefs(user);   // ✅ сохраняется
name.value = 'Петр';

Или toRef:

import { toRef } from 'vue';
const name = toRef(user, 'name');

Вложенная реактивность

ref:

const user = ref({
    name: 'Иван',
    address: {
        city: 'Москва',
    },
});

user.value.address.city = 'СПб';   // ✅ работает

reactive:

const user = reactive({
    name: 'Иван',
    address: { city: 'Москва' },
});

user.address.city = 'СПб';   // ✅ работает

Оба реактивны на любую глубину.

Реактивность в массивах

const items = ref(['a', 'b']);

items.value.push('c');         // ✅
items.value[0] = 'x';          // ✅
items.value = ['new'];         // ✅
items.value.length;            // реактивно
const items = reactive(['a', 'b']);

items.push('c');               // ✅
items[0] = 'x';                // ✅
items = ['new'];               // ❌ потеря реактивности

ref в шаблоне

<script setup>
import { ref } from 'vue';

const count = ref(0);
const user = ref({ name: 'Иван' });
</script>

<template>
    <p>{{ count }}</p>            <!-- без .value -->
    <p>{{ user.name }}</p>        <!-- Vue сам разворачивает -->
</template>

Vue автоматически разворачивает .value в шаблоне.

ref в объектах

const form = ref({
    name: ref(''),
    email: ref(''),
});

form.value.name = 'Иван';   // двойной .value — не работает

Правильно:

const form = reactive({
    name: '',
    email: '',
});

shallowRef — поверхностная реактивность

Только верхний уровень:

import { shallowRef } from 'vue';

const user = shallowRef({ name: 'Иван', age: 25 });
user.value.name = 'Петр';   // ❌ не триггерит обновление
user.value = { name: 'Петр' };  // ✅

Зачем: производительность для больших структур.

triggerRef — принудительное обновление

import { shallowRef, triggerRef } from 'vue';

const user = shallowRef({ name: 'Иван' });
user.value.name = 'Петр';
triggerRef(user);   // принудительно обновить

Практика

<script setup>
import { ref, reactive, toRefs } from 'vue';

// Примитив
const count = ref(0);

// Объект через ref
const user = ref({
    name: 'Иван',
    age: 25,
});

// Объект через reactive
const settings = reactive({
    theme: 'dark',
    language: 'ru',
});

// toRefs для деструктуризации
const { theme, language } = toRefs(settings);

function increment() {
    count.value++;
}

function updateUser() {
    user.value.name = 'Петр';
}

function changeTheme() {
    settings.theme = settings.theme === 'dark' ? 'light' : 'dark';
}
</script>

<template>
    <p>Счётчик: {{ count }}</p>
    <button @click="increment">+1</button>

    <p>Пользователь: {{ user.name }}, {{ user.age }}</p>
    <button @click="updateUser">Изменить имя</button>

    <p>Тема: {{ theme }}</p>
    <button @click="changeTheme">Переключить</button>
</template>

Частые ошибки

1. Забыл .value в JS:

const count = ref(0);
count++;   // ❌ не работает
count.value++;   // ✅

2. Деструктуризация reactive:

const { name } = user;   // ❌ теряет реактивность

3. Переприсвоение reactive:

user = {};   // ❌ потеря
user.name = '';   // ✅

4. .value в шаблоне:

{{ count.value }}   <!-- лишнее -->

Итог

  • ref — универсально, с .value в JS
  • reactive — для объектов, без .value
  • toRefs — деструктуризация с сохранением реактивности
  • shallowRef — только верхний уровень
  • Правило: для всего — ref

Дальше — toRefs и toRef.