Урок 03

toRefs и toRef

Проблема деструктуризации

reactive удобен, но при деструктуризации теряет реактивность:

import { reactive } from 'vue';

const user = reactive({
    name: 'Иван',
    age: 25,
});

const { name } = user;   // обычная строка, не реактивна
name;                     // 'Иван'
user.name = 'Петр';       // реактивность есть
console.log(name);        // всё ещё 'Иван'

Что не так: name — копия значения, не ссылка.

toRefs — решение

Преобразует reactive в объект с ref-ами:

import { reactive, toRefs } from 'vue';

const user = reactive({
    name: 'Иван',
    age: 25,
});

const { name, age } = toRefs(user);

name.value;               // 'Иван'
name.value = 'Петр';      // меняет user.name
user.name;                // 'Петр'

Каждое поле становится ref, связанным с user.

Когда использовать

1. Возврат из composable:

import { reactive, toRefs } from 'vue';

export function useUser() {
    const state = reactive({
        name: '',
        email: '',
        loading: false,
    });

    function load() {
        state.loading = true;
    }

    return { ...toRefs(state), load };
}

Использование:

<script setup>
import { useUser } from './composables/useUser';

const { name, email, loading, load } = useUser();
</script>

<template>
    <p v-if="loading">Загрузка...</p>
    <p>{{ name }} — {{ email }}</p>
</template>

2. Прокидывание в props:

const { title, count } = toRefs(state);

Теперь можно передать через v-model или как ref.

toRef — один ref

toRef — превращает одно поле reactive в ref.

import { reactive, toRef } from 'vue';

const user = reactive({ name: 'Иван', age: 25 });

const name = toRef(user, 'name');

name.value;              // 'Иван'
name.value = 'Петр';
user.name;               // 'Петр'

toRef для props

const props = defineProps({ title: String });

const title = toRef(props, 'title');

Зачем: можно передавать в composable.

toRef с дефолтом

const title = toRef(props, 'title', 'Default');

3-й аргумент — значение по умолчанию.

toRefs vs toRef

toRefstoRef
Длявсех полейодного поля
Результатобъект с ref-амиодин ref
Reactive-источникдада
Propsнетда

Практика — форма

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

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

const { name, email, message, agree } = toRefs(form);

function submit() {
    console.log('Отправлено:', form);
}

function reset() {
    form.name = '';
    form.email = '';
    form.message = '';
    form.agree = false;
}
</script>

<template>
    <form @submit.prevent="submit">
        <input v-model="name" placeholder="Имя">
        <input v-model="email" type="email" placeholder="Email">
        <textarea v-model="message" placeholder="Сообщение"></textarea>
        <label>
            <input type="checkbox" v-model="agree">
            Согласен
        </label>
        <button :disabled="!agree">Отправить</button>
        <button type="button" @click="reset">Сброс</button>
    </form>

    <pre>{{ form }}</pre>
</template>

v-model работает с name.value через v-model="name" — Vue сам разворачивает.

Composable с toRefs

import { ref, toRefs } from 'vue';

export function useCounter(initial = 0) {
    const state = ref({
        count: initial,
        history: [],
    });

    function increment() {
        state.value.count++;
        state.value.history.push(state.value.count);
    }

    function reset() {
        state.value.count = initial;
        state.value.history = [];
    }

    return {
        ...toRefs(state.value),
        increment,
        reset,
    };
}

Использование:

<script setup>
import { useCounter } from '@/composables/useCounter';

const { count, history, increment, reset } = useCounter(10);
</script>

<template>
    <p>Count: {{ count }}</p>
    <p>History: {{ history }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">Reset</button>
</template>

Работа с массивами

const state = reactive({ items: [1, 2, 3] });
const { items } = toRefs(state);

items.value.push(4);    // работает
items.value = [5];      // работает

toRef на computed

const fullName = computed(() => `${first.value} ${last.value}`);
const nameRef = toRef({ fullName }, 'fullName');

Редко нужно.

В связке с props

<script setup>
import { toRef, watch } from 'vue';

const props = defineProps({
    modelValue: String,
});

const value = toRef(props, 'modelValue');

watch(value, (val) => console.log('Changed:', val));
</script>

value — реактивная ссылка на prop.

Итог

  • toRefs(reactive) — объект с ref-ами, сохраняет реактивность
  • toRef(obj, key) — один ref
  • Используй toRefs при возврате из composable
  • toRef для props — чтобы следить за изменениями

Дальше — provide и inject.