Vue 3 предлагает два основных способа создать реактивные данные: ref() и reactive().
ref() оборачивает значение в реактивный объект с полем .value:
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Алексей')
const isLoading = ref(false)
const items = ref([]) // ref может хранить и объекты/массивы
// Доступ к значению — через .value (в JS-коде)
console.log(count.value) // 0
count.value++ // увеличить счётчик
count.value = 10 // установить значение
console.log(name.value) // 'Алексей'
name.value = 'Борис'
</script>
<template>
<!-- В шаблоне .value НЕ нужен — Vue разворачивает автоматически -->
<p>{{ count }}</p>
<p>{{ name }}</p>
</template>reactive() делает объект реактивным без обёртки в .value:
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'Алексей',
age: 25,
address: {
city: 'Москва'
}
})
// Доступ напрямую, без .value
console.log(user.name) // 'Алексей'
user.age++ // изменение напрямую
user.address.city = 'СПб' // вложенные объекты тоже реактивны
// Добавление новых свойств (Vue 3 — работает, в Vue 2 не работало)
user.email = 'alex@example.com'
</script>
<template>
<p>{{ user.name }}, {{ user.age }} лет</p>
<p>Город: {{ user.address.city }}</p>
</template>| Ситуация | Рекомендация |
|---|---|
| Примитивы (число, строка, boolean) | ref() |
| Одна переменная | ref() |
| Группа связанных данных | reactive() |
| Массив | ref() (проще заменять целиком) |
| Форма с несколькими полями | reactive() |
На практике многие разработчики используют ref() для всего — это унифицированный подход.
reactive() имеет важное ограничение — **нельзя заменить объект целиком**:
import { reactive } from 'vue'
let state = reactive({ count: 0 })
// НЕ РАБОТАЕТ — реактивность теряется
state = { count: 1 }
// РАБОТАЕТ — изменяем свойства существующего объекта
state.count = 1Также нельзя деструктурировать без потери реактивности:
import { reactive, toRefs } from 'vue'
const state = reactive({ x: 1, y: 2 })
// ПЛОХО — x и y теряют реактивность
const { x, y } = state
// ХОРОШО — используй toRefs
const { x, y } = toRefs(state)
console.log(x.value) // теперь x — реактивный refcomputed() создаёт производное реактивное значение:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
// Автоматически пересчитывается при изменении зависимостей
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
console.log(fullName.value) // 'Алексей Иванов'
firstName.value = 'Борис'
console.log(fullName.value) // 'Борис Иванов'
</script>Реализация ref и computed через замыкания — упрощённая модель того, как это работает в Vue
// Vue реализует ref и computed через сложную систему отслеживания зависимостей.
// Покажем упрощённую версию через замыкания.
// Простой ref — хранит значение и уведомляет подписчиков
function ref(initialValue) {
let _value = initialValue
const _subscribers = new Set()
return {
get value() {
return _value
},
set value(newVal) {
if (newVal !== _value) {
_value = newVal
// Уведомляем всех подписчиков об изменении
_subscribers.forEach(fn => fn())
}
},
// Подписаться на изменения (упрощённый аналог watch)
subscribe(fn) {
_subscribers.add(fn)
return () => _subscribers.delete(fn) // возвращаем функцию отписки
}
}
}
// computed — вычисляется лазово, кэширует результат
function computed(getter) {
let _cachedValue
let _isDirty = true // нужно ли пересчитать
return {
get value() {
if (_isDirty) {
_cachedValue = getter()
_isDirty = false
console.log(' [computed] пересчитан')
} else {
console.log(' [computed] из кэша')
}
return _cachedValue
},
// Пометить computed как "устаревший" (нужен пересчёт)
invalidate() {
_isDirty = true
}
}
}
// --- Демонстрация ---
const firstName = ref('Алексей')
const lastName = ref('Иванов')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// Подписываем computed на изменения ref'ов
const unsubFirst = firstName.subscribe(() => fullName.invalidate())
const unsubLast = lastName.subscribe(() => fullName.invalidate())
console.log('=== Первое чтение (пересчёт) ===')
console.log(fullName.value) // Алексей Иванов
console.log('\n=== Второе чтение (из кэша) ===')
console.log(fullName.value) // Алексей Иванов (из кэша)
console.log('\n=== Изменяем firstName ===')
firstName.value = 'Борис'
console.log(fullName.value) // Борис Иванов (пересчитан)
console.log('\n=== Читаем ещё раз ===')
console.log(fullName.value) // Борис Иванов (из кэша)
// Чистим подписки
unsubFirst()
unsubLast()Vue 3 предлагает два основных способа создать реактивные данные: ref() и reactive().
ref() оборачивает значение в реактивный объект с полем .value:
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Алексей')
const isLoading = ref(false)
const items = ref([]) // ref может хранить и объекты/массивы
// Доступ к значению — через .value (в JS-коде)
console.log(count.value) // 0
count.value++ // увеличить счётчик
count.value = 10 // установить значение
console.log(name.value) // 'Алексей'
name.value = 'Борис'
</script>
<template>
<!-- В шаблоне .value НЕ нужен — Vue разворачивает автоматически -->
<p>{{ count }}</p>
<p>{{ name }}</p>
</template>reactive() делает объект реактивным без обёртки в .value:
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'Алексей',
age: 25,
address: {
city: 'Москва'
}
})
// Доступ напрямую, без .value
console.log(user.name) // 'Алексей'
user.age++ // изменение напрямую
user.address.city = 'СПб' // вложенные объекты тоже реактивны
// Добавление новых свойств (Vue 3 — работает, в Vue 2 не работало)
user.email = 'alex@example.com'
</script>
<template>
<p>{{ user.name }}, {{ user.age }} лет</p>
<p>Город: {{ user.address.city }}</p>
</template>| Ситуация | Рекомендация |
|---|---|
| Примитивы (число, строка, boolean) | ref() |
| Одна переменная | ref() |
| Группа связанных данных | reactive() |
| Массив | ref() (проще заменять целиком) |
| Форма с несколькими полями | reactive() |
На практике многие разработчики используют ref() для всего — это унифицированный подход.
reactive() имеет важное ограничение — **нельзя заменить объект целиком**:
import { reactive } from 'vue'
let state = reactive({ count: 0 })
// НЕ РАБОТАЕТ — реактивность теряется
state = { count: 1 }
// РАБОТАЕТ — изменяем свойства существующего объекта
state.count = 1Также нельзя деструктурировать без потери реактивности:
import { reactive, toRefs } from 'vue'
const state = reactive({ x: 1, y: 2 })
// ПЛОХО — x и y теряют реактивность
const { x, y } = state
// ХОРОШО — используй toRefs
const { x, y } = toRefs(state)
console.log(x.value) // теперь x — реактивный refcomputed() создаёт производное реактивное значение:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
// Автоматически пересчитывается при изменении зависимостей
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
console.log(fullName.value) // 'Алексей Иванов'
firstName.value = 'Борис'
console.log(fullName.value) // 'Борис Иванов'
</script>Реализация ref и computed через замыкания — упрощённая модель того, как это работает в Vue
// Vue реализует ref и computed через сложную систему отслеживания зависимостей.
// Покажем упрощённую версию через замыкания.
// Простой ref — хранит значение и уведомляет подписчиков
function ref(initialValue) {
let _value = initialValue
const _subscribers = new Set()
return {
get value() {
return _value
},
set value(newVal) {
if (newVal !== _value) {
_value = newVal
// Уведомляем всех подписчиков об изменении
_subscribers.forEach(fn => fn())
}
},
// Подписаться на изменения (упрощённый аналог watch)
subscribe(fn) {
_subscribers.add(fn)
return () => _subscribers.delete(fn) // возвращаем функцию отписки
}
}
}
// computed — вычисляется лазово, кэширует результат
function computed(getter) {
let _cachedValue
let _isDirty = true // нужно ли пересчитать
return {
get value() {
if (_isDirty) {
_cachedValue = getter()
_isDirty = false
console.log(' [computed] пересчитан')
} else {
console.log(' [computed] из кэша')
}
return _cachedValue
},
// Пометить computed как "устаревший" (нужен пересчёт)
invalidate() {
_isDirty = true
}
}
}
// --- Демонстрация ---
const firstName = ref('Алексей')
const lastName = ref('Иванов')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// Подписываем computed на изменения ref'ов
const unsubFirst = firstName.subscribe(() => fullName.invalidate())
const unsubLast = lastName.subscribe(() => fullName.invalidate())
console.log('=== Первое чтение (пересчёт) ===')
console.log(fullName.value) // Алексей Иванов
console.log('\n=== Второе чтение (из кэша) ===')
console.log(fullName.value) // Алексей Иванов (из кэша)
console.log('\n=== Изменяем firstName ===')
firstName.value = 'Борис'
console.log(fullName.value) // Борис Иванов (пересчитан)
console.log('\n=== Читаем ещё раз ===')
console.log(fullName.value) // Борис Иванов (из кэша)
// Чистим подписки
unsubFirst()
unsubLast()Реализуй класс `Ref` с полем `value` через getter/setter, и функцию `computed(fn)` которая возвращает объект с `value` getter. При каждом чтении `computed.value` должна вызываться функция fn и возвращаться её результат (кэширование не требуется для этого задания — просто вычисляй каждый раз).
В конструкторе Ref пиши this._value = initialValue. Getter возвращает this._value, setter устанавливает this._value = newValue. Функция computed возвращает объект с get value() { return fn() } — геттер каждый раз вызывает fn.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке