Объект, созданный через reactive(), реактивен целиком. Но если вы деструктурируете его — извлечённые переменные **теряют реактивность**:
import { reactive } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
// ПРОБЛЕМА: деструктуризация разрывает реактивную связь!
const { count, name } = state
count // просто число 0 — не реактивное
name // просто строка — не реактивное
state.count = 5
console.log(count) // всё ещё 0, не обновилось!Почему это происходит? reactive() создаёт Proxy. При деструктуризации вы копируете текущее **примитивное значение**, а не ссылку на Proxy.
toRef(state, 'key') создаёт реактивный Ref, связанный с конкретным свойством объекта:
import { reactive, toRef } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
const count = toRef(state, 'count')
const name = toRef(state, 'name')
// Теперь count — это Ref, связанный с state.count
state.count = 5
console.log(count.value) // 5 — реактивная связь сохранена!
count.value = 10
console.log(state.count) // 10 — изменение через ref отражается в stateСвязь двусторонняя: изменение через ref обновляет исходный объект, и наоборот.
toRefs(state) принимает весь реактивный объект и возвращает объект, где каждое свойство — отдельный реактивный Ref:
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Алексей',
city: 'Москва',
})
// Разбиваем на рефы — теперь можно безопасно деструктурировать
const { count, name, city } = toRefs(state)
state.count = 5
console.log(count.value) // 5 — всё работает!
name.value = 'Борис'
console.log(state.name) // 'Борис'Главный практический кейс toRefs — это возврат реактивных данных из composable-функций:
// useUser.js
import { reactive, toRefs } from 'vue'
export function useUser() {
const state = reactive({
name: '',
email: '',
isLoading: false,
})
async function fetchUser(id) {
state.isLoading = true
const data = await api.getUser(id)
state.name = data.name
state.email = data.email
state.isLoading = false
}
// toRefs позволяет деструктурировать без потери реактивности
return { ...toRefs(state), fetchUser }
}
// В компоненте:
const { name, email, isLoading, fetchUser } = useUser()
// name, email, isLoading — реактивные рефы| | toRef | toRefs |
|---|---|---|
| Синтаксис | toRef(state, 'key') | toRefs(state) |
| Результат | Один Ref | Объект из Ref-ов |
| Когда использовать | Нужна ссылка на одно свойство | Нужно деструктурировать весь объект |
| Применение | Пропсы, отдельные поля | Composables, spread-возврат |
toRef удобен для создания writeable-ссылки на проп:
// В компоненте
const props = defineProps({ modelValue: Number })
const emit = defineEmits(['update:modelValue'])
// Создаём ref, который читает из props и пишет через emit
const localValue = toRef(props, 'modelValue')Демонстрация потери реактивности при деструктуризации и реализация toRef/toRefs через Proxy
// Демонстрируем проблему деструктуризации и реализуем toRef/toRefs
// Упрощённая реализация reactive через Proxy
function reactive(obj) {
return new Proxy(obj, {
get(target, key) { return target[key] },
set(target, key, value) {
target[key] = value
console.log(` [reactive] ${String(key)} = ${value}`)
return true
}
})
}
// Реализация toRef — создаёт объект с .value, связанный с source[key]
function toRef(source, key) {
return {
get value() {
return source[key]
},
set value(newVal) {
source[key] = newVal // пишем напрямую в source (это вызовет Proxy setter)
}
}
}
// Реализация toRefs — создаёт объект из toRef для каждого ключа
function toRefs(source) {
const refs = {}
for (const key in source) {
refs[key] = toRef(source, key)
}
return refs
}
// === Демонстрация проблемы ===
console.log('=== ПРОБЛЕМА: деструктуризация reactive ===')
const state = reactive({ count: 0, name: 'Алексей', city: 'Москва' })
// Деструктуризация — просто копируем примитивные значения
const { count, name } = state
console.log('Начальные значения: count =', count, ', name =', name)
state.count = 42
console.log('После state.count = 42:')
console.log(' state.count =', state.count) // 42
console.log(' count =', count) // 0 (!!!) — потеря реактивности
// === Решение: toRef ===
console.log('\n=== РЕШЕНИЕ: toRef ===')
const state2 = reactive({ score: 0, username: 'Борис' })
const score = toRef(state2, 'score')
const username = toRef(state2, 'username')
console.log('score.value =', score.value) // 0
state2.score = 100
console.log('После state2.score = 100:')
console.log(' score.value =', score.value) // 100 — реактивная связь!
score.value = 200
console.log('После score.value = 200:')
console.log(' state2.score =', state2.score) // 200 — двусторонняя связь!
// === Решение: toRefs ===
console.log('\n=== РЕШЕНИЕ: toRefs ===')
const state3 = reactive({ x: 10, y: 20, label: 'Точка A' })
const { x, y, label } = toRefs(state3)
console.log('x.value =', x.value, ', y.value =', y.value)
state3.x = 50
state3.y = 75
console.log('После изменения state3:')
console.log(' x.value =', x.value) // 50
console.log(' y.value =', y.value) // 75
label.value = 'Точка B'
console.log('После label.value = "Точка B":')
console.log(' state3.label =', state3.label) // 'Точка B'
// === Эмуляция composable ===
console.log('\n=== Паттерн composable с toRefs ===')
function useCounter(initial = 0) {
const state = reactive({ count: initial, step: 1 })
function increment() { state.count += state.step }
function setStep(n) { state.step = n }
return { ...toRefs(state), increment, setStep }
}
const { count: counter, step, increment, setStep } = useCounter(5)
console.log('counter.value =', counter.value) // 5
increment()
console.log('После increment(): counter.value =', counter.value) // 6
setStep(10)
increment()
console.log('После setStep(10) + increment(): counter.value =', counter.value) // 16Объект, созданный через reactive(), реактивен целиком. Но если вы деструктурируете его — извлечённые переменные **теряют реактивность**:
import { reactive } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
// ПРОБЛЕМА: деструктуризация разрывает реактивную связь!
const { count, name } = state
count // просто число 0 — не реактивное
name // просто строка — не реактивное
state.count = 5
console.log(count) // всё ещё 0, не обновилось!Почему это происходит? reactive() создаёт Proxy. При деструктуризации вы копируете текущее **примитивное значение**, а не ссылку на Proxy.
toRef(state, 'key') создаёт реактивный Ref, связанный с конкретным свойством объекта:
import { reactive, toRef } from 'vue'
const state = reactive({ count: 0, name: 'Алексей' })
const count = toRef(state, 'count')
const name = toRef(state, 'name')
// Теперь count — это Ref, связанный с state.count
state.count = 5
console.log(count.value) // 5 — реактивная связь сохранена!
count.value = 10
console.log(state.count) // 10 — изменение через ref отражается в stateСвязь двусторонняя: изменение через ref обновляет исходный объект, и наоборот.
toRefs(state) принимает весь реактивный объект и возвращает объект, где каждое свойство — отдельный реактивный Ref:
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Алексей',
city: 'Москва',
})
// Разбиваем на рефы — теперь можно безопасно деструктурировать
const { count, name, city } = toRefs(state)
state.count = 5
console.log(count.value) // 5 — всё работает!
name.value = 'Борис'
console.log(state.name) // 'Борис'Главный практический кейс toRefs — это возврат реактивных данных из composable-функций:
// useUser.js
import { reactive, toRefs } from 'vue'
export function useUser() {
const state = reactive({
name: '',
email: '',
isLoading: false,
})
async function fetchUser(id) {
state.isLoading = true
const data = await api.getUser(id)
state.name = data.name
state.email = data.email
state.isLoading = false
}
// toRefs позволяет деструктурировать без потери реактивности
return { ...toRefs(state), fetchUser }
}
// В компоненте:
const { name, email, isLoading, fetchUser } = useUser()
// name, email, isLoading — реактивные рефы| | toRef | toRefs |
|---|---|---|
| Синтаксис | toRef(state, 'key') | toRefs(state) |
| Результат | Один Ref | Объект из Ref-ов |
| Когда использовать | Нужна ссылка на одно свойство | Нужно деструктурировать весь объект |
| Применение | Пропсы, отдельные поля | Composables, spread-возврат |
toRef удобен для создания writeable-ссылки на проп:
// В компоненте
const props = defineProps({ modelValue: Number })
const emit = defineEmits(['update:modelValue'])
// Создаём ref, который читает из props и пишет через emit
const localValue = toRef(props, 'modelValue')Демонстрация потери реактивности при деструктуризации и реализация toRef/toRefs через Proxy
// Демонстрируем проблему деструктуризации и реализуем toRef/toRefs
// Упрощённая реализация reactive через Proxy
function reactive(obj) {
return new Proxy(obj, {
get(target, key) { return target[key] },
set(target, key, value) {
target[key] = value
console.log(` [reactive] ${String(key)} = ${value}`)
return true
}
})
}
// Реализация toRef — создаёт объект с .value, связанный с source[key]
function toRef(source, key) {
return {
get value() {
return source[key]
},
set value(newVal) {
source[key] = newVal // пишем напрямую в source (это вызовет Proxy setter)
}
}
}
// Реализация toRefs — создаёт объект из toRef для каждого ключа
function toRefs(source) {
const refs = {}
for (const key in source) {
refs[key] = toRef(source, key)
}
return refs
}
// === Демонстрация проблемы ===
console.log('=== ПРОБЛЕМА: деструктуризация reactive ===')
const state = reactive({ count: 0, name: 'Алексей', city: 'Москва' })
// Деструктуризация — просто копируем примитивные значения
const { count, name } = state
console.log('Начальные значения: count =', count, ', name =', name)
state.count = 42
console.log('После state.count = 42:')
console.log(' state.count =', state.count) // 42
console.log(' count =', count) // 0 (!!!) — потеря реактивности
// === Решение: toRef ===
console.log('\n=== РЕШЕНИЕ: toRef ===')
const state2 = reactive({ score: 0, username: 'Борис' })
const score = toRef(state2, 'score')
const username = toRef(state2, 'username')
console.log('score.value =', score.value) // 0
state2.score = 100
console.log('После state2.score = 100:')
console.log(' score.value =', score.value) // 100 — реактивная связь!
score.value = 200
console.log('После score.value = 200:')
console.log(' state2.score =', state2.score) // 200 — двусторонняя связь!
// === Решение: toRefs ===
console.log('\n=== РЕШЕНИЕ: toRefs ===')
const state3 = reactive({ x: 10, y: 20, label: 'Точка A' })
const { x, y, label } = toRefs(state3)
console.log('x.value =', x.value, ', y.value =', y.value)
state3.x = 50
state3.y = 75
console.log('После изменения state3:')
console.log(' x.value =', x.value) // 50
console.log(' y.value =', y.value) // 75
label.value = 'Точка B'
console.log('После label.value = "Точка B":')
console.log(' state3.label =', state3.label) // 'Точка B'
// === Эмуляция composable ===
console.log('\n=== Паттерн composable с toRefs ===')
function useCounter(initial = 0) {
const state = reactive({ count: initial, step: 1 })
function increment() { state.count += state.step }
function setStep(n) { state.step = n }
return { ...toRefs(state), increment, setStep }
}
const { count: counter, step, increment, setStep } = useCounter(5)
console.log('counter.value =', counter.value) // 5
increment()
console.log('После increment(): counter.value =', counter.value) // 6
setStep(10)
increment()
console.log('После setStep(10) + increment(): counter.value =', counter.value) // 16Реализуй функцию `toRef(source, key)`, которая возвращает объект с геттером и сеттером `.value`, связанным с `source[key]`. При чтении `.value` должно возвращаться текущее значение `source[key]`, при записи — обновляться `source[key]`. Затем реализуй `toRefs(source)`, которая возвращает объект из таких рефов для каждого ключа объекта.
Используй синтаксис get/set внутри объектного литерала: `return { get value() { return source[key] }, set value(v) { source[key] = v } }`. В `toRefs` используй `for...in` или `Object.keys(source)`.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке