Реактивность: Продвинутая
shallowRef()
Неглубокая реализация ref()
.
Тип
tsfunction shallowRef<T>(value: T): ShallowRef<T> interface ShallowRef<T> { value: T }
Подробности
В отличие от
ref()
, внутреннее значение неглубокого ref-объекта хранится и раскрывается как есть, и не становится глубоко реактивным. Реактивным является только изменение.value
.shallowRef()
обычно используется для оптимизации производительности больших структур данных или интеграции с внешними системами управления состоянием.Пример
jsconst state = shallowRef({ count: 1 }) // НЕ вызывает изменения state.value.count = 2 // вызывает изменения state.value = { count: 2 }
См. также
triggerRef()
Принудительный запуск эффектов, зависящих от неглубокого ref-объекта. Обычно это используется после глубоких изменений внутреннего значения неглубокого ref-объекта.
Тип
tsfunction triggerRef(ref: ShallowRef): void
Пример
jsconst shallow = shallowRef({ greet: 'Привет, мир' }) // Выведет в консоль "Привет, мир" один раз при первом проходе watchEffect(() => { console.log(shallow.value.greet) }) // Это не вызовет эффекта, поскольку ref-объект неглубокий shallow.value.greet = 'Привет, вселенная' // Выведет "Привет, вселенная" triggerRef(shallow)
customRef()
Создаёт пользовательский ref-объект с возможностью явно контролировать отслеживание зависимостей и управлять вызовом обновлений.
Тип
tsfunction 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:
jsimport { 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) } } }) }
Использование в компоненте:
vue<script setup> import { useDebouncedRef } from './debouncedRef' const text = useDebouncedRef('hello') </script> <template> <input v-model="text" /> </template>
Используйте осторожно
Когда мы используем
customRef
, нам следует быть осторожными с возвращаемым значением его геттера, особенно когда при каждом вызове геттера генерируется новый объектный тип данных. Это влияет на отношения между родительским и дочерним компонентами, где такойcustomRef
был передан в качестве пропса.Рендер-функция родительского компонента может быть запущена из-за изменений в другом реактивном состоянии. Во время повторного рендеринга значение нашего
customRef
переоценивается, возвращая новый объектный тип данных в качестве пропса дочернему компоненту. Этот пропс сравнивается с его предыдущим значением в дочернем компоненте, и поскольку они разные, реактивные зависимостиcustomRef
запускаются в дочернем компоненте. В это время реактивные зависимости в родительском компоненте не запускаются, потому что геттер customRef не был вызван, и его зависимости не были запущены в результате.
shallowReactive()
Неглубокая реализация reactive()
.
Тип
tsfunction shallowReactive<T extends object>(target: T): T
Подробности
В отличие от
reactive()
, здесь нет глубокого преобразования: для неглубокого реактивного объекта реактивными являются только свойства корневого уровня. Значения свойств хранятся и раскрываются как есть — это также означает, что свойства с ref-значениями не будут автоматически разворачиваться.Используйте с осторожностью
Неглубокие структуры данных следует использовать только для состояния корневого уровня компонента. Избегайте вложения их внутрь глубокого реактивного объекта, поскольку это создаёт дерево с непоследовательным поведением реактивности, которое трудно понять и отладить.
Пример
jsconst state = shallowReactive({ foo: 1, nested: { bar: 2 } }) // изменение корневых свойств состояния является реактивным state.foo++ // ...но не преобразует вложенные объекты isReactive(state.nested) // false // НЕ реактивно state.nested.bar++
shallowReadonly()
Неглубокая реализация readonly()
.
Тип
tsfunction shallowReadonly<T extends object>(target: T): Readonly<T>
Подробности
В отличие от
readonly()
, здесь нет глубокого преобразования: только свойства корневого уровня становятся доступными для чтения. Значения свойств хранятся и раскрываются как есть - это также означает, что свойства с ref-значениями не будут автоматически разворачиваться.Используйте с осторожностью
Неглубокие структуры данных следует использовать только для состояния корневого уровня компонента. Избегайте вложения их внутрь глубокого реактивного объекта, поскольку это создаёт дерево с непоследовательным поведением реактивности, которое трудно понять и отладить.
Пример
jsconst state = shallowReadonly({ foo: 1, nested: { bar: 2 } }) // изменение корневых свойств состояния не удастся state.foo++ // ...но работает с вложенными объектами isReadonly(state.nested) // false // работает state.nested.bar++
toRaw()
Возвращает исходный объект из прокси, созданный во Vue.
Тип
tsfunction toRaw<T>(proxy: T): T
Подробности
toRaw()
может возвращать исходный объект из прокси, созданных с помощьюreactive()
,readonly()
,shallowReactive()
илиshallowReadonly()
.Применяется в крайнем случае, когда требуется только чтение без доступа/отслеживания или запись без инициирования изменений. Не рекомендуется сохранять постоянную ссылку на оригинальный объект. Используйте с осторожностью.
Пример
jsconst foo = {} const reactiveFoo = reactive(foo) console.log(toRaw(reactiveFoo) === foo) // true
markRaw()
Помечает объект таким образом, что он никогда не будет преобразован в прокси. Возвращает сам объект.
Тип
tsfunction markRaw<T extends object>(value: T): T
Пример
jsconst foo = markRaw({}) console.log(isReactive(reactive(foo))) // false // также работает при вложении внутрь других реактивных объектов const bar = reactive({ foo }) console.log(isReactive(bar.foo)) // false
Используйте с осторожностью
markRaw()
и неглубокое API, такие какshallowReactive()
, позволяют выборочно отказаться от стандартного глубокого преобразования reactive/readonly и внедрить в граф состояний необработанные, непроксированные объекты. Они могут использоваться по разным причинам:Некоторые значение не должны быть реактивными. Например, сторонний комплексный экземпляр класса или объект компонента Vue.
Пропуск преобразования в прокси может улучшить производительность при отрисовке больших списков с иммутабельными (неизменяемыми) данными.
Пропуск преобразования — продвинутая техника, потому что опциональное отключение доступно только на корневом уровне. Если установить вложенный неотмеченный исходный объект в реактивный объект и получить к нему доступ, то вернётся его проксированная версия. Это может привести к опасности идентификации, то есть к выполнению операции, которая основывается на идентификации объекта, но использует как исходную, так и проксированную версию одного и того же объекта:
jsconst foo = markRaw({ nested: {} }) const bar = reactive({ // хотя `foo` отмечен как raw, foo.nested не будет таким. nested: foo.nested }) console.log(foo.nested === bar.nested) // false
С опасностью идентификации сталкиваются редко. Однако, чтобы правильно использовать эти API, избегая опасности идентификации, необходимо хорошо понимать принцип работы системы реактивности.
effectScope()
Создаёт объект области действия эффекта, который может захватывать другие реактивные эффекты (например, вычисляемые свойства и наблюдатели), созданные внутри него, чтобы иметь возможность уничтожить все эти эффекты вместе. Подробные примеры использования этого API приведены в соответствующем RFC.
Тип
tsfunction effectScope(detached?: boolean): EffectScope interface EffectScope { run<T>(fn: () => T): T | undefined // undefined если область действия неактивна stop(): void }
Пример
jsconst scope = effectScope() scope.run(() => { const doubled = computed(() => counter.value * 2) watch(doubled, () => console.log(doubled.value)) watchEffect(() => console.log('Count: ', doubled.value)) }) // для уничтожения всех эффектов в области действия scope.stop()
getCurrentScope()
Возвращает текущую активную область действия эффекта, если таковая есть.
Тип
tsfunction getCurrentScope(): EffectScope | undefined
onScopeDispose()
Регистрация коллбэка для текущей активной области действия эффекта. Коллбэк будет вызван, когда связанная с ним область действия эффекта будет остановлена.
Этот метод можно использовать как не связанную с компонентами замену onUnmounted
в переиспользуемых функциях композиций, поскольку функция setup()
каждого компонента Vue также вызывается в области действия эффекта.
Если эта функция будет вызвана без активной области действия эффекта, будет выведено предупреждение. В версии 3.5+ это предупреждение можно отключить, передав true
в качестве второго аргумента.
Тип
tsfunction onScopeDispose(fn: () => void, failSilently?: boolean): void