Реактивные ref ссылки

ref ссылки позволяют работать с любыми значениями, как с объектами, так и с примитивными типами данных

ref()

Функция ref() gолучает внутреннее значение и возвращает реактивный и мутируемый ref-объект. В этом ref-объекте есть только одно свойство .value, которое указывает на внутреннее значение. При использовании в шаблонах свойство .value не нужно указывать.

import { ref, computed } from "vue";

const a = ref(1);
const b = ref(2);

const sum = computed(() => a.value + b.value);
console.log(sum.value); // 3

a.value = 3;
console.log(sum.value); // 4

ref можно использовать и с объектами

Если свойству value ref-ссылки передаётся объект, то объект становится глубоко реактивным с помощью функции reactive.

import { ref, computed } from "vue";

const a = ref({ count: 1 });
const b = ref({ count: 2 });

const sum = computed(() => a.value.count + b.value.count);
console.log(sum.value); // 3

a.value.count = 3;
console.log(sum.value); // 4

isRef()

Проверяет является ли значение ref-объектом

import { isRef } from "vue";

const a = ref(1);
const b = 2;

console.log(isRef(a)); // true
console.log(isRef(b)); // false

unref()

Возвращает внутреннее значение, если аргумент является ref, в противном случае — сам аргумент. Это всего лишь синтаксический сахар для val = isRef(val) ? val.value : val.

import { unref } from "vue";

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x); // значение unwrapped гарантированно будет числом
}

toRef()

Функция toRef возвращает ref ссылку для одного из полей reactive объекта. Полученая ссылка сохраняет двустороннюю реактивную связь с исходным объектом.

import { reactive, toRef } from "vue";

const state = reactive({
  count: 10,  
}); 

const count = toRef(state, 'count');

console.log(count.value); // 10

state.count = 20;
console.log(count.value); // 20

count.value = 30;
console.log(state.count); // 30

toRef возвращает ref-ссылку пригодную для использования, даже если на данный момент свойства в источнике не существует. Это особенно полезно при работе с необязательными входными параметрами, которые не будут подхвачены toRefs.

toRefs()

Преобразует реактивный объект в обычный объект, в котором каждое свойство будет ref, указывающей на соответствующее свойство исходного объекта toRefs полезен при возвращении реактивного объекта из функции композиции, чтобы в компоненте использовать деструктуризацию/оператор разложения к возвращаемому объекту без потери реактивности

const state = reactive({
    foo: 1,
    bar: 2
  })
const { foo, bar } = toRefs(state)

state.foo++
console.log(foo.value) // 2

foo.value++
console.log(state.foo) // 3

shallowRef()

shallowRef() создает неглубокую ref ссылку переданного в функцию значения. В случае передачи в функцию примитивного типа разницы с ref() нет, ссылка будет реактивной. В случае передачи объекта - реактивнось будет реагировать на сам объект, но не на его поля.

Данный вариант например очень полезен при запросе данных с бекенда, как правило данные приходят единым массивом или объектом и целиком перезаписывает старые данные. В этом случае необходимо реагировать толька на изменение итогового объекта, а все вложеные поля никогда отдельно не меняются.

triggerRef()

triggerRef() выполняет эффекты, привязанные к shallowRef.

const shallow = shallowRef({
  greet: 'Привет, мир'
})

// Выведет "Привет, мир" один раз при первом проходе
watchEffect(() => {
  console.log(shallow.value.greet)
})

// Это не вызовет эффект, потому что ref-ссылка неглубокая
shallow.value.greet = 'Привет, вселенная'

// Выведет "Привет, вселенная"
triggerRef(shallow)

customRef()

Создаёт пользовательский ref-объект с возможностью явно контролировать отслеживание зависимостей и управлять вызовом обновлений.

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

Подробности

customRef() ожидает функцию-фабрику, которая получает в качестве аргументов функции track и trigger и должна возвращать объект с методами get и set.

В общем случае track() следует вызывать внутри get(), а trigger() - внутри set(). Однако вы имеете полный контроль над тем, когда их следует вызывать и следует ли вызывать вообще.

Пример

Создание debounce ref-объекта, который обновляет значение только по истечении определенного времени после последнего вызова set:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

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

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

<template>
  <input v-model="text" />
</template>

Используйте осторожно

Когда мы используем customRef, нам следует быть осторожными с возвращаемым значением его геттера, особенно когда при каждом вызове геттера генерируется новый объектный тип данных. Это влияет на отношения между родительским и дочерним компонентами, где такой customRef был передан в качестве пропса.

Рендер-функция родительского компонента может быть запущена из-за изменений в другом реактивном состоянии. Во время повторного рендеринга значение нашего customRef переоценивается, возвращая новый объектный тип данных в качестве пропса дочернему компоненту. Этот пропс сравнивается с его предыдущим значением в дочернем компоненте, и поскольку они разные, реактивные зависимости customRef запускаются в дочернем компоненте. В это время реактивные зависимости в родительском компоненте не запускаются, потому что геттер customRef не был вызван, и его зависимости не были запущены в результате.