По умолчанию 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 — подписчик уведомлён')Реализуй функцию `createShallowReactive(obj, onChange)`, которая возвращает Proxy над объектом, отслеживающий только изменения свойств **первого уровня**. При любом изменении корневого свойства вызывай `onChange(key, oldValue, newValue)`. Изменения во вложенных объектах отслеживать не нужно.
Создай Proxy с обработчиком set: `new Proxy(obj, { set(target, key, value) { const old = target[key]; target[key] = value; onChange(key, old, value); return true } })`. Вложенные объекты не нужно оборачивать — поверхностная реактивность работает только на первом уровне.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке