|
Что такое Composables?
Composables - это функция на Vue, которая использует Composition API [1] для инкапсуляции и повторного использования логики с состоянием. Это паттерн, который позволяет выносить реактивную логику из компонентов в отдельные переиспользуемые модули.
[Основные принципы]
1. Соглашения об именовании
- Имя файла: `use[Something].js` или `use[Something].ts` - Функция: `export function use[Something]()`
2. Базовая структура
// composables/useCounter.js import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) { const count = ref(initialValue)
const doubleCount = computed(() => count.value * 2)
const increment = () => { count.value++ }
const decrement = () => { count.value-- }
const reset = () => { count.value = initialValue }
// Возвращаем только то, что нужно использовать в компоненте return { count, doubleCount, increment, decrement, reset }
}
3. Использование в компоненте
< script setup> import { useCounter } from '@/composables/useCounter'
const { count, doubleCount, increment, decrement, reset } = useCounter(10)
< /script>
< template>
< div>
< p>Count: {{ count }}p>
< p>Double: {{ doubleCount }}< /p>
< button @click="increment">+< /button>
< button @click="decrement">-< /button>
< button @click="reset">Reset< /button>
< /div>
< /template>
[Реальные примеры composables]
1. Управление сообщениями строки статуса
// composables/useStatus.js import { ref } from 'vue'
const statusMessage = ref('') const statusType = ref('') let timeoutId = null
export function useStatus() { const showStatus = (message, type = 'info', duration = 5000) => { statusMessage.value = message statusType.value = type
if (timeoutId) { clearTimeout(timeoutId) }
if (duration > 0) { timeoutId = setTimeout(() => { statusMessage.value = '' statusType.value = '' }, duration) }
console.log(`[${type.toUpperCase()}] ${message}`) }
const hideStatus = () => { if (timeoutId) { clearTimeout(timeoutId) } statusMessage.value = '' statusType.value = '' }
return { statusMessage, statusType, showStatus, hideStatus }
}
2. Работа с localStorage
// composables/useLocalStorage.js import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) { const storedValue = localStorage.getItem(key) const data = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
watch(data, (newValue) => { localStorage.setItem(key, JSON.stringify(newValue)) }, { deep: true })
const remove = () => { localStorage.removeItem(key) data.value = defaultValue }
return { data, remove }
}
3. Работа с API (загрузка данных)
// composables/useFetch.js import { ref, shallowRef } from 'vue'
export function useFetch(url) { const data = shallowRef(null) const error = ref(null) const loading = ref(false)
const fetchData = async () => { loading.value = true error.value = null
try { const response = await fetch(url) if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) data.value = await response.json() } catch (err) { error.value = err.message } finally { loading.value = false } }
return { data, error, loading, fetchData }
}
4. Управление WebSocket
// composables/useWebSocket.js import { ref, onUnmounted } from 'vue'
export function useWebSocket(url) { const socket = ref(null) const isConnected = ref(false) const lastMessage = ref(null) const error = ref(null)
const connect = () => { if (socket.value && socket.value.readyState === WebSocket.OPEN) { return }
socket.value = new WebSocket(url)
socket.value.onopen = () => { isConnected.value = true console.log('WebSocket connected') }
socket.value.onmessage = (event) => { lastMessage.value = event.data }
socket.value.onerror = (event) => { error.value = event console.error('WebSocket error:', event) }
socket.value.onclose = () => { isConnected.value = false console.log('WebSocket disconnected') } }
const sendMessage = (message) => { if (socket.value && socket.value.readyState === WebSocket.OPEN) { socket.value.send(message) } else { console.warn('WebSocket is not connected') } }
const disconnect = () => { if (socket.value) { socket.value.close() socket.value = null } }
onUnmounted(() => { disconnect() })
return { socket, isConnected, lastMessage, error, connect, sendMessage, disconnect }
}
5. Обработка форм и валидация
// composables/useForm.js import { ref, reactive, computed } from 'vue'
export function useForm(initialValues, validationRules = {}) { const formData = reactive({ ...initialValues }) const errors = ref({}) const touched = ref({})
const validate = () => { const newErrors = {}
for (const [field, rules] of Object.entries(validationRules)) { const value = formData[field]
for (const rule of rules) { const error = rule(value, formData) if (error) { newErrors[field] = error break } } }
errors.value = newErrors return Object.keys(newErrors).length === 0 }
const isValid = computed(() => { return Object.keys(errors.value).length === 0 })
const handleFieldChange = (field) => { touched.value[field] = true validate() }
const reset = () => { Object.assign(formData, initialValues) errors.value = {} touched.value = {} }
return { formData, errors, touched, isValid, validate, handleFieldChange, reset }
}
6. Управление таймерами и интервалами
// composables/useInterval.js import { ref, onUnmounted } from 'vue'
export function useInterval(callback, delay) { const intervalId = ref(null) const isRunning = ref(false)
const start = () => { if (intervalId.value !== null) return
intervalId.value = setInterval(() => { callback() }, delay)
isRunning.value = true }
const stop = () => { if (intervalId.value) { clearInterval(intervalId.value) intervalId.value = null isRunning.value = false } }
onUnmounted(() => { stop() })
return { isRunning, start, stop }
}
[Преимущества использования Composables]
1. Переиспользование кода - одна логика может использоваться в нескольких компонентах. 2. Тестируемость - composables легко тестировать изолированно. 3. Организация кода - компоненты становятся чище и фокусируются на представлении. 4. Совместное использование состояния - можно создать глобальное состояние (как в `useStatus`). 5. Типизация - легко добавить TypeScript.
Лучшие практики:
1. Используйте ref для примитивов и reactive для объектов
const count = ref(0) const user = reactive({ name: 'John', age: 30 })
2. Возвращайте только то, что нужно
return { // ref и reactive - реактивные данные count, user, // computed - вычисляемые значения doubleCount, // функции - методы increment, reset
}
3. Используйте onUnmounted для очистки
onUnmounted(() => { clearInterval(intervalId) socket.close()
})
4. Документируйте composables
/** * Управление счетчиком * @param {number} initialValue - начальное значение * @returns {Object} - объект с состоянием и методами */ export function useCounter(initialValue = 0) { // ...
}
5. Именуйте с префиксом "use"
// ✅ правильноe xport function useLocalStorage() {} export function useFetch() {} export function useForm() {}
// ❌ неправильно export function localStorage() {} export function fetch() {} export function form() {}
[Продвинутые техники]
1. Композиция composables
// composables/useUserProfile.js import { useFetch } from './useFetch' import { useLocalStorage } from './useLocalStorage'
export function useUserProfile(userId) { const { data: user, loading, fetchData } = useFetch(`/api/users/${userId}`) const { data: preferences, update: updatePreferences } = useLocalStorage('user_preferences', {})
const updateProfile = async (updates) => { await fetchData() // логика обновления }
return { user, preferences, loading, updateProfile, updatePreferences }
}
2. Глобальные composables с provide/inject
// composables/useTheme.js import { ref, provide, inject } from 'vue'
const themeSymbol = Symbol('theme')
export function useThemeProvider() { const theme = ref('light')
const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light' }
provide(themeSymbol, { theme, toggleTheme })
return { theme, toggleTheme }
}
export function useTheme() { const theme = inject(themeSymbol) if (!theme) { throw new Error('useTheme must be used within useThemeProvider') } return theme
}
Composables - это мощный инструмент для создания чистого, переиспользуемого и поддерживаемого кода в Vue 3 приложениях.
[Ссылки]
1. Vue Composition API. |