**VueUse** — это коллекция из более чем 200 готовых composable-функций для Vue 3. Авторы следят за браузерными API и оборачивают их в реактивные примитивы Vue. Вместо того чтобы писать одни и те же composables снова и снова в каждом проекте, вы берёте готовые, проверенные и типизированные реализации.
npm install @vueuse/coreСинхронизирует реактивное состояние с localStorage. При перезагрузке страницы значение восстанавливается автоматически:
import { useLocalStorage } from '@vueuse/core'
const theme = useLocalStorage('theme', 'light') // значение по умолчанию — 'light'
const user = useLocalStorage('user', { name: '', logged: false })
// Работает как обычный ref
theme.value = 'dark' // автоматически сохраняется в localStorage
console.log(theme.value) // 'dark' — даже после перезагрузкиОтслеживает позицию мыши в реальном времени:
import { useMouse } from '@vueuse/core'
const { x, y, sourceType } = useMouse()
// x и y — реактивные ref с текущими координатами
// sourceType: 'mouse' | 'touch'<template>
<div>Позиция мыши: {{ x }}, {{ y }}</div>
</template>Реактивная обёртка над Fetch API с автоматическим управлением состоянием загрузки:
import { useFetch } from '@vueuse/core'
const { data, error, isFetching, execute } = useFetch(
'https://api.example.com/users'
).json()
// data — реактивные данные ответа
// isFetching — true пока идёт запрос
// error — ошибка если запрос провалился
// execute() — повторить запрос
// С reactive URL (перезапускает запрос при изменении):
const userId = ref(1)
const { data: user } = useFetch(
computed(() => `/api/users/${userId.value}`)
).json()Безопасно добавляет обработчики событий и автоматически удаляет их при unmount компонента:
import { useEventListener } from '@vueuse/core'
// В Options API приходилось вручную removeEventListener в beforeUnmount
// VueUse делает это за вас:
useEventListener(window, 'resize', () => {
console.log('Окно изменило размер:', window.innerWidth)
})
useEventListener(document, 'keydown', (e) => {
if (e.key === 'Escape') closeModal()
})Управление тёмной темой с синхронизацией с системными настройками и localStorage:
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark() // реактивный boolean
const toggleDark = useToggle(isDark)
// isDark автоматически добавляет/убирает класс 'dark' на <html>
// Следит за prefers-color-scheme медиа-запросомОпределяет когда элемент попадает в поле зрения (viewport):
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
const target = ref(null) // ref на DOM-элемент
const isVisible = ref(false)
useIntersectionObserver(target, ([{ isIntersecting }]) => {
isVisible.value = isIntersecting
})Понять принцип VueUse проще, если написать аналог самому:
// useWindowSize — аналог @vueuse/core
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
function update() {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => window.addEventListener('resize', update))
onUnmounted(() => window.removeEventListener('resize', update))
return { width, height }
}Реализация аналогов useLocalStorage, useMouse и useEventListener на чистом JavaScript — чтобы понять, что делает VueUse под капотом
// ============================================
// Аналог useLocalStorage — реактивное хранилище
// ============================================
// В Vue это был бы ref, здесь используем паттерн Observable
function useLocalStorage(key, defaultValue) {
// Пытаемся загрузить сохранённое значение
let stored
try {
const raw = localStorage.getItem(key)
stored = raw !== null ? JSON.parse(raw) : defaultValue
} catch {
stored = defaultValue
}
let _value = stored
const subscribers = []
const storage = {
get value() {
return _value
},
set value(newVal) {
_value = newVal
// Синхронизируем с localStorage
try {
localStorage.setItem(key, JSON.stringify(newVal))
} catch (e) {
console.warn('localStorage недоступен:', e.message)
}
// Уведомляем подписчиков (аналог реактивности Vue)
for (const fn of subscribers) fn(newVal)
},
onChange(fn) {
subscribers.push(fn)
return () => {
const idx = subscribers.indexOf(fn)
if (idx !== -1) subscribers.splice(idx, 1)
}
},
}
return storage
}
// Демонстрация useLocalStorage
console.log('=== useLocalStorage ===')
const theme = useLocalStorage('app-theme', 'light')
console.log('Начальное значение:', theme.value) // 'light' (или из localStorage)
const unsubscribe = theme.onChange((val) => {
console.log('Тема изменилась на:', val)
})
theme.value = 'dark' // сохраняется в localStorage + уведомляет подписчиков
theme.value = 'light'
unsubscribe() // отписываемся
theme.value = 'system' // подписчик не вызовется
console.log('Итоговое значение:', theme.value)
// ============================================
// Аналог useEventListener — с auto-cleanup
// ============================================
console.log('\n=== useEventListener ===')
// В браузере это был бы настоящий addEventListener.
// Здесь симулируем EventEmitter.
class EventBus {
constructor() { this._handlers = {} }
addEventListener(event, handler) {
if (!this._handlers[event]) this._handlers[event] = []
this._handlers[event].push(handler)
console.log(` [addEventListener] "${event}" зарегистрирован`)
}
removeEventListener(event, handler) {
if (!this._handlers[event]) return
this._handlers[event] = this._handlers[event].filter(h => h !== handler)
console.log(` [removeEventListener] "${event}" удалён`)
}
emit(event, payload) {
for (const h of (this._handlers[event] || [])) h(payload)
}
}
const fakeWindow = new EventBus()
function useEventListener(target, event, handler) {
target.addEventListener(event, handler)
// Возвращаем функцию очистки (аналог onUnmounted в Vue)
return function cleanup() {
target.removeEventListener(event, handler)
}
}
const onResize = useEventListener(fakeWindow, 'resize', (e) => {
console.log(' Событие resize:', e)
})
const onKeydown = useEventListener(fakeWindow, 'keydown', (e) => {
console.log(' Событие keydown:', e.key)
})
// Симулируем события
fakeWindow.emit('resize', { width: 1200, height: 800 })
fakeWindow.emit('keydown', { key: 'Escape' })
// Симулируем unmount — очищаем слушатели
console.log('\n [component unmount] — очищаем слушатели...')
onResize()
onKeydown()
// После cleanup — события игнорируются
fakeWindow.emit('resize', { width: 800, height: 600 })
console.log(' (resize событие не обработано — слушатель удалён)')
// ============================================
// Аналог useDark — тёмная тема
// ============================================
console.log('\n=== useDark ===')
function useDark(storageKey = 'color-scheme') {
// Определяем начальное значение
// В браузере: window.matchMedia('(prefers-color-scheme: dark)').matches
const systemPrefersDark = false // симуляция
const stored = (() => {
try { return localStorage.getItem(storageKey) } catch { return null }
})()
let isDark = stored !== null ? stored === 'dark' : systemPrefersDark
const state = {
get value() { return isDark },
set value(val) {
isDark = val
// В реальном VueUse: document.documentElement.classList.toggle('dark', val)
console.log(` document.documentElement.classList: ${val ? 'добавлен' : 'удалён'} класс "dark"`)
try { localStorage.setItem(storageKey, val ? 'dark' : 'light') } catch {}
},
toggle() {
this.value = !this.value
}
}
return state
}
const isDark = useDark()
console.log('Текущая тема тёмная?', isDark.value)
isDark.value = true
isDark.toggle()
console.log('После двух переключений:', isDark.value)**VueUse** — это коллекция из более чем 200 готовых composable-функций для Vue 3. Авторы следят за браузерными API и оборачивают их в реактивные примитивы Vue. Вместо того чтобы писать одни и те же composables снова и снова в каждом проекте, вы берёте готовые, проверенные и типизированные реализации.
npm install @vueuse/coreСинхронизирует реактивное состояние с localStorage. При перезагрузке страницы значение восстанавливается автоматически:
import { useLocalStorage } from '@vueuse/core'
const theme = useLocalStorage('theme', 'light') // значение по умолчанию — 'light'
const user = useLocalStorage('user', { name: '', logged: false })
// Работает как обычный ref
theme.value = 'dark' // автоматически сохраняется в localStorage
console.log(theme.value) // 'dark' — даже после перезагрузкиОтслеживает позицию мыши в реальном времени:
import { useMouse } from '@vueuse/core'
const { x, y, sourceType } = useMouse()
// x и y — реактивные ref с текущими координатами
// sourceType: 'mouse' | 'touch'<template>
<div>Позиция мыши: {{ x }}, {{ y }}</div>
</template>Реактивная обёртка над Fetch API с автоматическим управлением состоянием загрузки:
import { useFetch } from '@vueuse/core'
const { data, error, isFetching, execute } = useFetch(
'https://api.example.com/users'
).json()
// data — реактивные данные ответа
// isFetching — true пока идёт запрос
// error — ошибка если запрос провалился
// execute() — повторить запрос
// С reactive URL (перезапускает запрос при изменении):
const userId = ref(1)
const { data: user } = useFetch(
computed(() => `/api/users/${userId.value}`)
).json()Безопасно добавляет обработчики событий и автоматически удаляет их при unmount компонента:
import { useEventListener } from '@vueuse/core'
// В Options API приходилось вручную removeEventListener в beforeUnmount
// VueUse делает это за вас:
useEventListener(window, 'resize', () => {
console.log('Окно изменило размер:', window.innerWidth)
})
useEventListener(document, 'keydown', (e) => {
if (e.key === 'Escape') closeModal()
})Управление тёмной темой с синхронизацией с системными настройками и localStorage:
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark() // реактивный boolean
const toggleDark = useToggle(isDark)
// isDark автоматически добавляет/убирает класс 'dark' на <html>
// Следит за prefers-color-scheme медиа-запросомОпределяет когда элемент попадает в поле зрения (viewport):
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
const target = ref(null) // ref на DOM-элемент
const isVisible = ref(false)
useIntersectionObserver(target, ([{ isIntersecting }]) => {
isVisible.value = isIntersecting
})Понять принцип VueUse проще, если написать аналог самому:
// useWindowSize — аналог @vueuse/core
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
function update() {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => window.addEventListener('resize', update))
onUnmounted(() => window.removeEventListener('resize', update))
return { width, height }
}Реализация аналогов useLocalStorage, useMouse и useEventListener на чистом JavaScript — чтобы понять, что делает VueUse под капотом
// ============================================
// Аналог useLocalStorage — реактивное хранилище
// ============================================
// В Vue это был бы ref, здесь используем паттерн Observable
function useLocalStorage(key, defaultValue) {
// Пытаемся загрузить сохранённое значение
let stored
try {
const raw = localStorage.getItem(key)
stored = raw !== null ? JSON.parse(raw) : defaultValue
} catch {
stored = defaultValue
}
let _value = stored
const subscribers = []
const storage = {
get value() {
return _value
},
set value(newVal) {
_value = newVal
// Синхронизируем с localStorage
try {
localStorage.setItem(key, JSON.stringify(newVal))
} catch (e) {
console.warn('localStorage недоступен:', e.message)
}
// Уведомляем подписчиков (аналог реактивности Vue)
for (const fn of subscribers) fn(newVal)
},
onChange(fn) {
subscribers.push(fn)
return () => {
const idx = subscribers.indexOf(fn)
if (idx !== -1) subscribers.splice(idx, 1)
}
},
}
return storage
}
// Демонстрация useLocalStorage
console.log('=== useLocalStorage ===')
const theme = useLocalStorage('app-theme', 'light')
console.log('Начальное значение:', theme.value) // 'light' (или из localStorage)
const unsubscribe = theme.onChange((val) => {
console.log('Тема изменилась на:', val)
})
theme.value = 'dark' // сохраняется в localStorage + уведомляет подписчиков
theme.value = 'light'
unsubscribe() // отписываемся
theme.value = 'system' // подписчик не вызовется
console.log('Итоговое значение:', theme.value)
// ============================================
// Аналог useEventListener — с auto-cleanup
// ============================================
console.log('\n=== useEventListener ===')
// В браузере это был бы настоящий addEventListener.
// Здесь симулируем EventEmitter.
class EventBus {
constructor() { this._handlers = {} }
addEventListener(event, handler) {
if (!this._handlers[event]) this._handlers[event] = []
this._handlers[event].push(handler)
console.log(` [addEventListener] "${event}" зарегистрирован`)
}
removeEventListener(event, handler) {
if (!this._handlers[event]) return
this._handlers[event] = this._handlers[event].filter(h => h !== handler)
console.log(` [removeEventListener] "${event}" удалён`)
}
emit(event, payload) {
for (const h of (this._handlers[event] || [])) h(payload)
}
}
const fakeWindow = new EventBus()
function useEventListener(target, event, handler) {
target.addEventListener(event, handler)
// Возвращаем функцию очистки (аналог onUnmounted в Vue)
return function cleanup() {
target.removeEventListener(event, handler)
}
}
const onResize = useEventListener(fakeWindow, 'resize', (e) => {
console.log(' Событие resize:', e)
})
const onKeydown = useEventListener(fakeWindow, 'keydown', (e) => {
console.log(' Событие keydown:', e.key)
})
// Симулируем события
fakeWindow.emit('resize', { width: 1200, height: 800 })
fakeWindow.emit('keydown', { key: 'Escape' })
// Симулируем unmount — очищаем слушатели
console.log('\n [component unmount] — очищаем слушатели...')
onResize()
onKeydown()
// После cleanup — события игнорируются
fakeWindow.emit('resize', { width: 800, height: 600 })
console.log(' (resize событие не обработано — слушатель удалён)')
// ============================================
// Аналог useDark — тёмная тема
// ============================================
console.log('\n=== useDark ===')
function useDark(storageKey = 'color-scheme') {
// Определяем начальное значение
// В браузере: window.matchMedia('(prefers-color-scheme: dark)').matches
const systemPrefersDark = false // симуляция
const stored = (() => {
try { return localStorage.getItem(storageKey) } catch { return null }
})()
let isDark = stored !== null ? stored === 'dark' : systemPrefersDark
const state = {
get value() { return isDark },
set value(val) {
isDark = val
// В реальном VueUse: document.documentElement.classList.toggle('dark', val)
console.log(` document.documentElement.classList: ${val ? 'добавлен' : 'удалён'} класс "dark"`)
try { localStorage.setItem(storageKey, val ? 'dark' : 'light') } catch {}
},
toggle() {
this.value = !this.value
}
}
return state
}
const isDark = useDark()
console.log('Текущая тема тёмная?', isDark.value)
isDark.value = true
isDark.toggle()
console.log('После двух переключений:', isDark.value)Реализуй функцию `useCounter(initialValue, options)`, которая ведёт себя как VueUse-composable. Она должна возвращать объект с: полем `count` (начальное значение = initialValue или 0), методами `increment()`, `decrement()`, `reset()` и `set(n)`. Если в options передан `{ min: number, max: number }`, то `count` не должен выходить за эти пределы.
Напиши вспомогательную функцию clamp(val, min, max): если min и max не заданы (undefined), возвращай val без изменений. Иначе: Math.min(max, Math.max(min, val)). Используй её в каждом методе после изменения count.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке