По умолчанию ref() и reactive() создают глубокую (deep) реактивность: Vue отслеживает изменения на всех уровнях вложенности объекта.
const state = reactive({
user: {
profile: {
settings: {
theme: 'dark'
}
}
}
})
// Vue отслеживает изменение даже на 4-м уровне
state.user.profile.settings.theme = 'light' // вызовет ре-рендерДля этого Vue рекурсивно оборачивает каждый вложенный объект в Proxy. Это удобно, но может быть дорогостоящим для больших структур данных.
shallowRef() отслеживает только замену самого значения (изменение .value), но не изменения внутри объекта:
import { shallowRef } from 'vue'
const state = shallowRef({ count: 0, nested: { value: 'a' } })
// Это НЕ вызовет ре-рендер — внутренние изменения не отслеживаются
state.value.count = 1
state.value.nested.value = 'b'
// Это вызовет ре-рендер — мы меняем само .value
state.value = { count: 1, nested: { value: 'b' } }shallowReactive() делает реактивными только свойства первого уровня объекта:
import { shallowReactive } from 'vue'
const state = shallowReactive({
name: 'Алексей',
config: {
theme: 'dark',
language: 'ru'
}
})
// Это вызовет ре-рендер — корневое свойство
state.name = 'Борис'
// Это НЕ вызовет ре-рендер — вложенный объект
state.config.theme = 'light' // Vue не отслеживает это
// Замена всего объекта — вызовет ре-рендер
state.config = { theme: 'light', language: 'en' }Если вы изменили внутренние данные shallowRef, но хотите принудительно запустить обновление, используйте triggerRef():
import { shallowRef, triggerRef } from 'vue'
const list = shallowRef([1, 2, 3])
// Мутируем массив напрямую (не заменяем .value)
list.value.push(4)
// DOM не обновится автоматически
triggerRef(list) // принудительно запускаем обновлениеИспользуй `shallowRef` / `shallowReactive` когда:
1. Большие неизменяемые структуры — например, список из тысяч записей, который всегда заменяется целиком (данные с API).
2. Внешние объекты без реактивности — библиотечные объекты (Chart.js, Three.js, Mapbox), которые управляют собой сами. Vue не должен их оборачивать в Proxy.
3. Нормализованное хранилище — когда вы сами управляете обновлениями и знаете, что нужно обновить.
// Хороший кейс для shallowRef: данные с сервера
const users = shallowRef([])
async function fetchUsers() {
const data = await api.getUsers()
users.value = data // заменяем целиком — один ре-рендер
}// Хороший кейс для shallowReactive: компонент с библиотечным объектом
const chartState = shallowReactive({
chart: null, // экземпляр Chart.js — не нужна глубокая реактивность
data: [],
isLoading: false
})Сравнение глубокой и поверхностной реактивности через Proxy — что отслеживается, а что нет
// Сравниваем deep reactive и shallow reactive
// Глубокая реактивность — рекурсивно оборачивает вложенные объекты
function deepReactive(obj, onChange, path = '') {
if (typeof obj !== 'object' || obj === null) return obj
// Рекурсивно оборачиваем все вложенные объекты
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
obj[key] = deepReactive(obj[key], onChange, path ? `${path}.${key}` : key)
}
}
return new Proxy(obj, {
set(target, key, value) {
const fullPath = path ? `${path}.${String(key)}` : String(key)
const oldVal = target[key]
if (typeof value === 'object' && value !== null) {
value = deepReactive(value, onChange, fullPath)
}
target[key] = value
onChange(fullPath, oldVal, value)
return true
}
})
}
// Поверхностная реактивность — только первый уровень
function shallowReactive(obj, onChange) {
return new Proxy(obj, {
set(target, key, value) {
const oldVal = target[key]
target[key] = value
onChange(String(key), oldVal, value)
return true
}
// Вложенные объекты НЕ оборачиваются — только первый уровень
})
}
const log = (label) => (path, oldVal, newVal) => {
console.log(` [${label}] "${path}" изменено: ${JSON.stringify(oldVal)} -> ${JSON.stringify(newVal)}`)
}
// === Тест глубокой реактивности ===
console.log('=== deepReactive (как reactive()) ===')
const deepState = deepReactive({
name: 'Алексей',
address: {
city: 'Москва',
coords: { lat: 55.7, lng: 37.6 }
}
}, log('deep'))
deepState.name = 'Борис' // отслеживается
deepState.address.city = 'Питер' // отслеживается
deepState.address.coords.lat = 59.9 // отслеживается (4-й уровень!)
// === Тест поверхностной реактивности ===
console.log('\n=== shallowReactive() ===')
const shallowState = shallowReactive({
name: 'Алексей',
address: {
city: 'Москва',
coords: { lat: 55.7, lng: 37.6 }
}
}, log('shallow'))
shallowState.name = 'Борис' // отслеживается (корневое свойство)
shallowState.address.city = 'Питер' // НЕ отслеживается — вложено
shallowState.address.coords.lat = 59.9 // НЕ отслеживается — вложено
// Но замена всего объекта — отслеживается
shallowState.address = { city: 'Питер', coords: { lat: 59.9, lng: 30.3 } }
// === triggerRef эмуляция ===
console.log('\n=== Эмуляция shallowRef + triggerRef ===')
function createShallowRef(initial) {
let _value = initial
const subscribers = new Set()
const ref = {
get value() { return _value },
set value(newVal) {
_value = newVal
this._notify()
},
_notify() {
console.log(` [shallowRef] Обновление! Текущее значение: ${JSON.stringify(_value)}`)
subscribers.forEach(fn => fn(_value))
},
subscribe(fn) { subscribers.add(fn) }
}
return ref
}
function triggerRef(ref) {
ref._notify()
}
const list = createShallowRef([1, 2, 3])
list.subscribe(v => console.log(` [subscriber] Список: [${v.join(', ')}]`))
list.value.push(4) // мутация — подписчик не вызывается
console.log('После push(4) без triggerRef — подписчик молчит')
console.log('Но данные изменились:', list.value)
triggerRef(list) // принудительно уведомляем
console.log('После triggerRef — подписчик уведомлён')По умолчанию ref() и reactive() создают глубокую (deep) реактивность: Vue отслеживает изменения на всех уровнях вложенности объекта.
const state = reactive({
user: {
profile: {
settings: {
theme: 'dark'
}
}
}
})
// Vue отслеживает изменение даже на 4-м уровне
state.user.profile.settings.theme = 'light' // вызовет ре-рендерДля этого Vue рекурсивно оборачивает каждый вложенный объект в Proxy. Это удобно, но может быть дорогостоящим для больших структур данных.
shallowRef() отслеживает только замену самого значения (изменение .value), но не изменения внутри объекта:
import { shallowRef } from 'vue'
const state = shallowRef({ count: 0, nested: { value: 'a' } })
// Это НЕ вызовет ре-рендер — внутренние изменения не отслеживаются
state.value.count = 1
state.value.nested.value = 'b'
// Это вызовет ре-рендер — мы меняем само .value
state.value = { count: 1, nested: { value: 'b' } }shallowReactive() делает реактивными только свойства первого уровня объекта:
import { shallowReactive } from 'vue'
const state = shallowReactive({
name: 'Алексей',
config: {
theme: 'dark',
language: 'ru'
}
})
// Это вызовет ре-рендер — корневое свойство
state.name = 'Борис'
// Это НЕ вызовет ре-рендер — вложенный объект
state.config.theme = 'light' // Vue не отслеживает это
// Замена всего объекта — вызовет ре-рендер
state.config = { theme: 'light', language: 'en' }Если вы изменили внутренние данные shallowRef, но хотите принудительно запустить обновление, используйте triggerRef():
import { shallowRef, triggerRef } from 'vue'
const list = shallowRef([1, 2, 3])
// Мутируем массив напрямую (не заменяем .value)
list.value.push(4)
// DOM не обновится автоматически
triggerRef(list) // принудительно запускаем обновлениеИспользуй `shallowRef` / `shallowReactive` когда:
1. Большие неизменяемые структуры — например, список из тысяч записей, который всегда заменяется целиком (данные с API).
2. Внешние объекты без реактивности — библиотечные объекты (Chart.js, Three.js, Mapbox), которые управляют собой сами. Vue не должен их оборачивать в Proxy.
3. Нормализованное хранилище — когда вы сами управляете обновлениями и знаете, что нужно обновить.
// Хороший кейс для shallowRef: данные с сервера
const users = shallowRef([])
async function fetchUsers() {
const data = await api.getUsers()
users.value = data // заменяем целиком — один ре-рендер
}// Хороший кейс для shallowReactive: компонент с библиотечным объектом
const chartState = shallowReactive({
chart: null, // экземпляр Chart.js — не нужна глубокая реактивность
data: [],
isLoading: false
})Сравнение глубокой и поверхностной реактивности через Proxy — что отслеживается, а что нет
// Сравниваем deep reactive и shallow reactive
// Глубокая реактивность — рекурсивно оборачивает вложенные объекты
function deepReactive(obj, onChange, path = '') {
if (typeof obj !== 'object' || obj === null) return obj
// Рекурсивно оборачиваем все вложенные объекты
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
obj[key] = deepReactive(obj[key], onChange, path ? `${path}.${key}` : key)
}
}
return new Proxy(obj, {
set(target, key, value) {
const fullPath = path ? `${path}.${String(key)}` : String(key)
const oldVal = target[key]
if (typeof value === 'object' && value !== null) {
value = deepReactive(value, onChange, fullPath)
}
target[key] = value
onChange(fullPath, oldVal, value)
return true
}
})
}
// Поверхностная реактивность — только первый уровень
function shallowReactive(obj, onChange) {
return new Proxy(obj, {
set(target, key, value) {
const oldVal = target[key]
target[key] = value
onChange(String(key), oldVal, value)
return true
}
// Вложенные объекты НЕ оборачиваются — только первый уровень
})
}
const log = (label) => (path, oldVal, newVal) => {
console.log(` [${label}] "${path}" изменено: ${JSON.stringify(oldVal)} -> ${JSON.stringify(newVal)}`)
}
// === Тест глубокой реактивности ===
console.log('=== deepReactive (как reactive()) ===')
const deepState = deepReactive({
name: 'Алексей',
address: {
city: 'Москва',
coords: { lat: 55.7, lng: 37.6 }
}
}, log('deep'))
deepState.name = 'Борис' // отслеживается
deepState.address.city = 'Питер' // отслеживается
deepState.address.coords.lat = 59.9 // отслеживается (4-й уровень!)
// === Тест поверхностной реактивности ===
console.log('\n=== shallowReactive() ===')
const shallowState = shallowReactive({
name: 'Алексей',
address: {
city: 'Москва',
coords: { lat: 55.7, lng: 37.6 }
}
}, log('shallow'))
shallowState.name = 'Борис' // отслеживается (корневое свойство)
shallowState.address.city = 'Питер' // НЕ отслеживается — вложено
shallowState.address.coords.lat = 59.9 // НЕ отслеживается — вложено
// Но замена всего объекта — отслеживается
shallowState.address = { city: 'Питер', coords: { lat: 59.9, lng: 30.3 } }
// === triggerRef эмуляция ===
console.log('\n=== Эмуляция shallowRef + triggerRef ===')
function createShallowRef(initial) {
let _value = initial
const subscribers = new Set()
const ref = {
get value() { return _value },
set value(newVal) {
_value = newVal
this._notify()
},
_notify() {
console.log(` [shallowRef] Обновление! Текущее значение: ${JSON.stringify(_value)}`)
subscribers.forEach(fn => fn(_value))
},
subscribe(fn) { subscribers.add(fn) }
}
return ref
}
function triggerRef(ref) {
ref._notify()
}
const list = createShallowRef([1, 2, 3])
list.subscribe(v => console.log(` [subscriber] Список: [${v.join(', ')}]`))
list.value.push(4) // мутация — подписчик не вызывается
console.log('После push(4) без triggerRef — подписчик молчит')
console.log('Но данные изменились:', list.value)
triggerRef(list) // принудительно уведомляем
console.log('После triggerRef — подписчик уведомлён')Создай демо, показывающее разницу между reactive и shallowReactive. Используй shallowRef для большого массива данных и triggerRef для принудительного обновления. При клике на "Изменить вложенные данные" UI должен обновиться только после triggerRef.
Замени первый ??? на shallowRef, второй на shallowReactive, третий на triggerRef(items). shallowRef отслеживает только .value, а не содержимое массива. shallowReactive отслеживает только корневые свойства объекта.