customRef дає змогу створити ref з власною логікою відстеження й сповіщення про зміни. Фабрика отримує дві функції:
track()- зареєструвати залежність (викликати при читанні);trigger()- сповістити залежних про зміну (викликати, коли значення справді змінилося).
Класичний приклад - ref із затримкою (debounce):
import { customRef } from 'vue';
export function useDebouncedRef(initial, delay = 300) {
let value = initial;
let timeout;
return customRef((track, trigger) => ({
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
}));
}
<script setup>
const query = useDebouncedRef('', 400);
watch(query, (value) => search(value)); // запит лише після паузи у введенні
</script>
<template>
<input v-model="query">
</template>
Поле оновлюється в шаблоні... тільки після затримки - бо значення змінюється в set із запізненням. Для пошуку це прийнятно, а для звичайного введення - ні: користувач не бачитиме набраного тексту. Тому частіше роблять інакше: звичайний ref для поля і окремий debounced-ref (чи watch з затримкою) для запиту.
Інші застосування customRef:
- ref, синхронізований з
localStorage- читання з кешу, запис у сховище; - ref над зовнішнім джерелом (значення з бібліотеки, що має власні події змін) -
trigger()з обробника події бібліотеки; - валідація чи нормалізація при записі (обрізати пробіли, обмежити діапазон);
- лінива ініціалізація дорогого значення при першому читанні.
Що варто знати:
getмає повертати однакове значення для однакового стану - Vue може викликати його багато разів;- не забувати
track()- без нього шаблон не оновиться, хочаtrigger()викликано; - документація окремо застерігає від
get, що щоразу створює новий об'єкт: якщо такий ref передано в props, кожен рендер батька дає дочірньому компоненту «нове» значення, і той перерендерюється без потреби; - готові варіанти вже є у VueUse:
refDebounced,useLocalStorage,refThrottled.