Vue

Утилиты реактивности: toRef, toRefs, unref

Композабл возвращает reactive-объект с полями x и y координат мыши. В компоненте разработчик пишет const { x, y } = useMouse() и удивляется: значения замёрзли на первоначальных. Деструктуризация reactive-объекта разрывает связь с Proxy, и x, y становятся обычными числами-снимками. Решение - toRefs: он превращает каждое поле reactive-объекта в отдельный ref, и деструктуризация сохраняет реактивность. Этот же приём лежит в основе storeToRefs из Pinia.

  • Композаблы вроде useMouse или useWindowSize из VueUse возвращают набор реактивных значений, которые хочется деструктурировать
  • Деструктуризация props внутри setup без потери реактивности отдельного поля
  • storeToRefs в Pinia: вытащить state и getters стора в локальные ref
  • Утилитарные функции, принимающие 'ref или просто значение', где unref снимает обёртку единообразно
  • Передача одного поля reactive-объекта в другой композабл как самостоятельного ref через toRef

Предварительные знания

  • Различие ref (значение в .value) и reactive (Proxy на объект)
  • Понимание, что деструктуризация копирует значения, а не ссылки на свойства
  • Базовое знакомство с props в script setup

Почему деструктуризация ломает реактивность

Реактивность reactive-объекта держится на Proxy: перехват идёт при обращении к свойству через сам объект. Деструктуризация const { x } = state читает значение свойства один раз и кладёт в новую переменную. С этого момента x - обычное число, никак не связанное с Proxy. Изменение state.x обновит объект, но локальная x останется прежней.

Та же ловушка с props. Деструктуризация const { count } = props внутри setup даёт снимок значения на момент деструктуризации, а не реактивную ссылку на проп (за исключением реактивной деструктуризации props в более новых сборках, но базовый механизм тот же). Чтобы сохранить связь, поле нужно превратить обратно в ref.

Ошибка коварна тем, что код выглядит рабочим: значения корректны при первом рендере. Проблема всплывает позже, когда источник меняется, а деструктурированная переменная остаётся замороженной.

Почему const { x } = reactiveState теряет реактивность?

toRef и toRefs

toRefs проходит по всем свойствам reactive-объекта и для каждого создаёт ref, привязанный к исходному свойству. Чтение .value такого ref идёт в объект, запись - обратно в него. После toRefs деструктуризация безопасна: каждая переменная остаётся реактивным ref.

toRef работает точечно: создаёт один ref на конкретное свойство. Это нужно, когда из объекта берётся только одно поле или когда поле передаётся в другой композабл как самостоятельный ref. В Vue 3.3 toRef умеет принимать функцию-геттер, превращая любое вычисляемое выражение в ref только для чтения.

УтилитаЧто делаетКогда применять
toRefsВсе свойства объекта -> объект из refДеструктуризация всего reactive-объекта или возврата композабла
toRef(obj, key)Одно свойство -> ref, связанный с нимНужен один реактивный ref из поля объекта
toRef(getter)Геттер -> readonly refПревратить вычисление в ref для передачи дальше

Композабл хранит состояние в reactive-объекте, а в компоненте его деструктурируют. Что вернуть из композабла?

unref, isRef и аналогия со storeToRefs

unref - короткая форма проверки: если аргумент это ref, вернуть его .value, иначе вернуть сам аргумент. Это удобно в утилитарных функциях, которые должны принимать и ref, и обычное значение, не заставляя вызывающего разворачивать вручную. isRef отвечает на вопрос строго: является ли значение ref, и используется для ветвления логики.

storeToRefs из Pinia - прямое применение этой идеи к стору. Сам стор это reactive-объект, поэтому деструктуризация его state и getters так же теряет реактивность. storeToRefs оборачивает state и getters в ref (но не методы-actions, которые и так привязаны), позволяя деструктурировать стор безопасно.

Логика та же, что у toRefs: реактивные данные нужно превращать в ref перед деструктуризацией. Pinia лишь добавляет правило не трогать actions - функции и так стабильны, оборачивать их в ref не имеет смысла.

Зачем нужен unref в утилитарной функции?

Связь с другими темами

Урок завершает набор утилит реактивности и готовит к Pinia:

  • ref и reactive — Все утилиты этого урока - мост между двумя примитивами реактивности
  • shallowRef и toRaw — toRaw из соседнего урока относится к тому же семейству утилит вокруг реактивных объектов
  • Pinia — storeToRefs - прямое применение идеи toRefs к стору состояния

Итог

  • Деструктуризация reactive-объекта разрывает реактивность: поля становятся обычными значениями-снимками
  • toRefs превращает каждое свойство reactive-объекта в отдельный ref, так что деструктуризация сохраняет связь с источником
  • toRef создаёт один ref, привязанный к конкретному свойству reactive-объекта или к источнику в виде функции-геттера
  • unref возвращает значение из ref или само значение, если это не ref - удобно для функций, принимающих и то и другое
  • isRef проверяет, является ли значение ref; storeToRefs в Pinia применяет toRefs к стору для безопасной деструктуризации

Связанные уроки

  • vue-06-ref-reactive — Утилиты строятся вокруг ref и reactive, поэтому нужно твёрдо понимать оба примитива
  • vue-19-shallow-raw — toRaw из соседнего урока - часть того же набора утилит вокруг реактивных объектов
  • vue-27-pinia-intro — storeToRefs в Pinia делает ровно то же, что toRefs: позволяет деструктурировать стор без потери реактивности
Утилиты реактивности: toRef, toRefs, unref

0

1

Войти