Если вы уже освоили основы написания Composable в Vue, то следующий шаг — собрать коллекцию лучших и самых полезных паттернов, расширив свой инструментарий для решения задач:
-
Паттерны для улучшения управления состоянием
-
Организация Composable (не всегда нужен отдельный файл!)
-
Улучшение опыта разработчика, например поддержка одновременно асинхронного и синхронного поведения
В этой статье мы рассмотрим семь различных паттернов для написания более эффективных Composable.
1. Паттерн Data Store (Хранилище данных)
Проблема: Как управлять глобальным состоянием, избегая дублирования и обеспечивая контролируемый доступ?
Решение: Создать реактивное хранилище в модульной области видимости, экспортируя только необходимые части.
import { reactive, toRefs, readonly } from 'vue'; import { themes } from './utils'; // 1. Глобальное состояние, существующее в рамках модуля // (будет общим для всех вызовов composable) const state = reactive({ darkMode: false, sidebarCollapsed: false, // 2. Приватное поле, не экспортируется наружу theme: 'nord' }); export default () => { // 2. Экспортируем только часть состояния const { darkMode, sidebarCollapsed } = toRefs(state); // 3. Метод для изменения приватного поля const changeTheme = (newTheme) => { if (themes.includes(newTheme)) { // Обновляем если тема валидна state.theme = newTheme; } }; return { // 2. Возращаем только часть состояния darkMode, sidebarCollapsed, // 2. Возращаем версию для чтения theme: readonly(state.theme), // 3. Возращаем метода для изменения базового состояния changeTheme }; };
Итог:
-
Изоляция глобального состояния.
-
Защита приватных данных через
readonly. -
Единая точка управления логикой.
2. Thin Composables («Тонкие» Composable)
Проблема: Как отделить бизнес-логику от реактивности для улучшения тестируемости?
Решение: Вынести логику в чистые функции, а в Composable оставить только реактивность.
import { ref, watch } from 'vue'; import { convertToFahrenheit } from './temperatureConversion'; export function useTemperatureConverter(celsiusRef) { const fahrenheit = ref(0); watch(celsiusRef, (newCelsius) => { // Логика конвертации вынесена в отдельный модуль fahrenheit.value = convertToFahrenheit(newCelsius); }); return { fahrenheit }; }
Итог:
-
Бизнес-логика не зависит от фреймворка.
-
Composable становится «прослойкой» для реактивности.
3. Inline Composables (Встроенные Composable)
Проблема: Нам не всегда нужно извлекать логику в отдельный файл.
Решение: Для простых случаев создавайте Composable прямо в компоненте.
const useCount = (i) => { const count = ref(0); const increment = () => count.value += 1; const decrement = () => count.value -= 1; return { id: i, count, increment, decrement, }; }; const listOfCounters = []; for (const i = 0; i < 10; i++) { listOfCounters.push(useCount(i)); }
В шаблоне мы можем использовать счетчики по отдельности:
<div v-for="counter in listOfCounters" :key="counter.id"> <button @click="counter.decrement()">-</button> {{ counter.count }} <button @click="counter.increment()">+</button> </div>
Итог:
-
Упрощение структуры для локальной логики.
-
Избегаем избыточных файлов.
4. Dynamic Return (Динамический возврат значений)
Проблема: Как сделать API Composable гибким для разных сценариев?
Решение: Сделать опцию для расширенного режима и возвращать либо одно значение, либо значение с методами.
// Пример 1: Простой возврат значения const timer = useTimer(60);
Однако бывают ситуации, когда нам требуется больше контроля и дополнительные значения или методы для компоновки.
// Пример 2: Расширенный API const { timer, pause, reset } = useTimer(60, { controls: true // Опция для расширенного режима });
Итог:
-
Адаптация под потребности компонента.
-
Уменьшение сложности API по умолчанию.
5. Flexible Arguments (Гибкие аргументы)
Проблема: Как одновременно работать и с реактивными, и с обычными значениями?
Решение: Использовать ref() и toValue() для автоматической конвертации.
import { ref, toValue } from 'vue'; export function useSearch(url, search) { // 1. Всегда получаем ref, даже если передано не значение а тоже ref const searchQuery = ref(search); // 2. toValue() получим значение даже если передан не ref а обычное значение const results = computed(() => { return fetchResults(toValue(url), toValue(searchQuery)); }); return { searchQuery, results }; }
Итог:
-
Единый интерфейс для любых типов аргументов.
-
Упрощение интеграции с внешними данными.
6. Async + Sync (Поддержка обоих режимов)
Проблема: Как сделать Composable полезным как для асинхронных, так и для синхронных сценариев?
Решение: Возвращать объект с реактивными данными и промисом.
import { ref } from 'vue'; import { ofetch } from 'ofetch'; function useAsyncOrSync() { // Синхронное значение будет немедленно возвращено const data = ref(null); // Асинхронная операция const asyncOperationPromise = ofetch( 'https://api.example.com/data' ) .then(response => { // Реактивное обновление данных по ref data.value = response; return { data }; }); // Объединяем промис и реактивные данные const enhancedPromise = Object.assign(asyncOperationPromise, { data, }); return enhancedPromise; } // Использование: // Если мы используем его синхронно, то сразу же получаем значение, // которое мы инициализировали с помощью data . // Затем, когда Promise наконец выполняется, значение обновится. const { data } = useAsyncOrSync(); // Синхронный доступ //Или просто await, и вам вообще не придётся иметь дело со null const { data } = await useAsyncOrSync(); // Асинхронный доступ
Итог:
-
Гибкость использования в разных контекстах.
-
Реактивное обновление данных после разрешения промиса.
7. Options Object (Объект настроек)
Проблема: Как избежать длинных списков параметров?
Решение: Заменить аргументы на объект с именованными свойствами.
// До: Сложно запомнить порядок параметров useRefHistory(state, true, 10); // После: Самодокументирующийся код useRefHistory(state, { deep: true, // Рекурсивное отслеживание capacity: 10 // Лимит истории });
Реализация:
export function useRefHistory(source, options = {}) { const { deep = false, capacity = 100 } = options; // ...логика }
Итог:
-
Читаемость и расширяемость.
-
Избегаем «мусорных» параметров.
Заключение
Эти семь паттернов помогут вам создавать Composable, которые:
-
Управляют состоянием без хаоса.
-
Разделяют ответственность между логикой и реактивностью.
-
Адаптируются под разные сценарии использования.
Дополнительные материалы:
ссылка на оригинал статьи https://habr.com/ru/articles/904818/
Добавить комментарий