Vue composables, общее руководство Печать
Добавил(а) microsin   

Что такое 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.