**Computed** отлично подходит для производных данных. Но иногда при изменении данных нужно выполнить **побочный эффект**: отправить запрос на сервер, записать в localStorage, обновить заголовок страницы и т.д. Для этого используются watchers.
watch() наблюдает за конкретным источником данных:
<script setup>
import { ref, watch } from 'vue'
const searchQuery = ref('')
const results = ref([])
// Отслеживаем конкретный ref
watch(searchQuery, async (newValue, oldValue) => {
console.log(`Поиск изменился: "${oldValue}" -> "${newValue}"`)
if (newValue.length >= 2) {
results.value = await fetchResults(newValue)
} else {
results.value = []
}
})
</script>// immediate: true — запустить немедленно (не ждать первого изменения)
watch(searchQuery, (newVal) => {
console.log('Запрос:', newVal)
}, { immediate: true })
// deep: true — отслеживать вложенные изменения в объекте
const user = ref({ name: 'Алексей', address: { city: 'Москва' } })
watch(user, (newVal) => {
console.log('Пользователь изменился:', newVal)
}, { deep: true })
// Без deep: true изменение user.value.address.city не вызовет watch
// once: true (Vue 3.4+) — сработает только один раз
watch(searchQuery, (newVal) => {
console.log('Первое изменение:', newVal)
}, { once: true })import { ref, watch } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
// Массив источников
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Имя: ${oldFirst} -> ${newFirst}`)
console.log(`Фамилия: ${oldLast} -> ${newLast}`)
})const user = reactive({ name: 'Алексей', age: 25 })
// Нельзя так: watch(user.name, ...) — потеряем реактивность
// Нужно использовать getter-функцию:
watch(() => user.name, (newName) => {
console.log('Имя изменилось:', newName)
})watchEffect() сам определяет зависимости — отслеживает все реактивные значения, к которым обратились внутри функции:
import { ref, watchEffect } from 'vue'
const userId = ref(1)
const userData = ref(null)
// Запускается немедленно и при изменении любой зависимости
watchEffect(async () => {
// Vue автоматически отследит, что мы читаем userId.value
const data = await fetch(`/api/users/${userId.value}`)
userData.value = await data.json()
})
// При изменении userId запрос повторится автоматически
userId.value = 2Обе функции возвращают функцию остановки:
const stop = watch(count, (newVal) => {
console.log(newVal)
})
// Позже — остановить наблюдение
stop()
const stopEffect = watchEffect(() => {
console.log(count.value)
})
stopEffect()| | watch | watchEffect | computed |
|---|---|---|---|
| Зависимости | Явные | Автоматические | Автоматические |
| Первый запуск | При изменении (или immediate) | Сразу | При чтении |
| Доступ к старому значению | Да | Нет | Нет |
| Возвращает значение | Нет | Нет | Да |
| Когда использовать | Async, side effects | Отслеживание нескольких | Производные данные |
Реализация простого watch через Proxy — перехват изменений и вызов колбэков
// Реализуем систему наблюдателей на основе Proxy.
// При изменении любого свойства объекта вызываются
// соответствующие обработчики.
function createReactive(obj) {
// Map: ключ свойства -> массив колбэков
const watchers = new Map()
const proxy = new Proxy(obj, {
set(target, key, newValue) {
const oldValue = target[key]
target[key] = newValue
// Вызываем все watchers для этого свойства
if (watchers.has(key) && newValue !== oldValue) {
watchers.get(key).forEach(fn => fn(newValue, oldValue))
}
// Вызываем watchers для '*' (наблюдение за любыми изменениями)
if (watchers.has('*')) {
watchers.get('*').forEach(fn => fn(key, newValue, oldValue))
}
return true
}
})
// Метод watch возвращает функцию отписки
function watch(key, callback, options = {}) {
if (!watchers.has(key)) {
watchers.set(key, [])
}
watchers.get(key).push(callback)
// immediate: true — запустить немедленно
if (options.immediate) {
callback(proxy[key], undefined)
}
// Возвращаем функцию остановки
return function stop() {
const handlers = watchers.get(key)
const index = handlers.indexOf(callback)
if (index > -1) handlers.splice(index, 1)
}
}
return { proxy, watch }
}
// --- Демонстрация ---
const { proxy: state, watch } = createReactive({
count: 0,
name: 'Vue',
loading: false
})
// Наблюдаем за count
const stopCount = watch('count', (newVal, oldVal) => {
console.log(`count: ${oldVal} -> ${newVal}`)
})
// Наблюдаем за name с immediate
watch('name', (newVal, oldVal) => {
console.log(`name: ${oldVal ?? 'нет'} -> ${newVal}`)
}, { immediate: true })
// Наблюдаем за всеми изменениями
watch('*', (key, newVal, oldVal) => {
console.log(`[any] ${String(key)}: ${oldVal} -> ${newVal}`)
})
console.log('\n--- Изменения ---')
state.count = 1 // вызовет watch('count') и watch('*')
state.count = 2
state.name = 'Vue 3' // вызовет watch('name') и watch('*')
console.log('\n--- Остановка watcher count ---')
stopCount()
state.count = 3 // watch('count') уже не вызовется, но watch('*') — да**Computed** отлично подходит для производных данных. Но иногда при изменении данных нужно выполнить **побочный эффект**: отправить запрос на сервер, записать в localStorage, обновить заголовок страницы и т.д. Для этого используются watchers.
watch() наблюдает за конкретным источником данных:
<script setup>
import { ref, watch } from 'vue'
const searchQuery = ref('')
const results = ref([])
// Отслеживаем конкретный ref
watch(searchQuery, async (newValue, oldValue) => {
console.log(`Поиск изменился: "${oldValue}" -> "${newValue}"`)
if (newValue.length >= 2) {
results.value = await fetchResults(newValue)
} else {
results.value = []
}
})
</script>// immediate: true — запустить немедленно (не ждать первого изменения)
watch(searchQuery, (newVal) => {
console.log('Запрос:', newVal)
}, { immediate: true })
// deep: true — отслеживать вложенные изменения в объекте
const user = ref({ name: 'Алексей', address: { city: 'Москва' } })
watch(user, (newVal) => {
console.log('Пользователь изменился:', newVal)
}, { deep: true })
// Без deep: true изменение user.value.address.city не вызовет watch
// once: true (Vue 3.4+) — сработает только один раз
watch(searchQuery, (newVal) => {
console.log('Первое изменение:', newVal)
}, { once: true })import { ref, watch } from 'vue'
const firstName = ref('Алексей')
const lastName = ref('Иванов')
// Массив источников
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Имя: ${oldFirst} -> ${newFirst}`)
console.log(`Фамилия: ${oldLast} -> ${newLast}`)
})const user = reactive({ name: 'Алексей', age: 25 })
// Нельзя так: watch(user.name, ...) — потеряем реактивность
// Нужно использовать getter-функцию:
watch(() => user.name, (newName) => {
console.log('Имя изменилось:', newName)
})watchEffect() сам определяет зависимости — отслеживает все реактивные значения, к которым обратились внутри функции:
import { ref, watchEffect } from 'vue'
const userId = ref(1)
const userData = ref(null)
// Запускается немедленно и при изменении любой зависимости
watchEffect(async () => {
// Vue автоматически отследит, что мы читаем userId.value
const data = await fetch(`/api/users/${userId.value}`)
userData.value = await data.json()
})
// При изменении userId запрос повторится автоматически
userId.value = 2Обе функции возвращают функцию остановки:
const stop = watch(count, (newVal) => {
console.log(newVal)
})
// Позже — остановить наблюдение
stop()
const stopEffect = watchEffect(() => {
console.log(count.value)
})
stopEffect()| | watch | watchEffect | computed |
|---|---|---|---|
| Зависимости | Явные | Автоматические | Автоматические |
| Первый запуск | При изменении (или immediate) | Сразу | При чтении |
| Доступ к старому значению | Да | Нет | Нет |
| Возвращает значение | Нет | Нет | Да |
| Когда использовать | Async, side effects | Отслеживание нескольких | Производные данные |
Реализация простого watch через Proxy — перехват изменений и вызов колбэков
// Реализуем систему наблюдателей на основе Proxy.
// При изменении любого свойства объекта вызываются
// соответствующие обработчики.
function createReactive(obj) {
// Map: ключ свойства -> массив колбэков
const watchers = new Map()
const proxy = new Proxy(obj, {
set(target, key, newValue) {
const oldValue = target[key]
target[key] = newValue
// Вызываем все watchers для этого свойства
if (watchers.has(key) && newValue !== oldValue) {
watchers.get(key).forEach(fn => fn(newValue, oldValue))
}
// Вызываем watchers для '*' (наблюдение за любыми изменениями)
if (watchers.has('*')) {
watchers.get('*').forEach(fn => fn(key, newValue, oldValue))
}
return true
}
})
// Метод watch возвращает функцию отписки
function watch(key, callback, options = {}) {
if (!watchers.has(key)) {
watchers.set(key, [])
}
watchers.get(key).push(callback)
// immediate: true — запустить немедленно
if (options.immediate) {
callback(proxy[key], undefined)
}
// Возвращаем функцию остановки
return function stop() {
const handlers = watchers.get(key)
const index = handlers.indexOf(callback)
if (index > -1) handlers.splice(index, 1)
}
}
return { proxy, watch }
}
// --- Демонстрация ---
const { proxy: state, watch } = createReactive({
count: 0,
name: 'Vue',
loading: false
})
// Наблюдаем за count
const stopCount = watch('count', (newVal, oldVal) => {
console.log(`count: ${oldVal} -> ${newVal}`)
})
// Наблюдаем за name с immediate
watch('name', (newVal, oldVal) => {
console.log(`name: ${oldVal ?? 'нет'} -> ${newVal}`)
}, { immediate: true })
// Наблюдаем за всеми изменениями
watch('*', (key, newVal, oldVal) => {
console.log(`[any] ${String(key)}: ${oldVal} -> ${newVal}`)
})
console.log('\n--- Изменения ---')
state.count = 1 // вызовет watch('count') и watch('*')
state.count = 2
state.name = 'Vue 3' // вызовет watch('name') и watch('*')
console.log('\n--- Остановка watcher count ---')
stopCount()
state.count = 3 // watch('count') уже не вызовется, но watch('*') — даРеализуй функцию `createStore(initialState)`, которая возвращает объект с двумя полями: `state` (реактивный Proxy) и `watch(key, callback)`. При изменении `state[key]` должен вызываться `callback(newVal, oldVal)`. Метод `watch` должен возвращать функцию отписки.
Инициализируй watchers как пустой объект {}. В Proxy set handler: const oldValue = target[key]; target[key] = value; if (watchers[key]) watchers[key].forEach(fn => fn(value, oldValue)). В watch: if (!watchers[key]) watchers[key] = []; watchers[key].push(callback); return () => { watchers[key] = watchers[key].filter(fn => fn !== callback) }.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке