Создание composables

Composables представляют собой функции, которые инкапсулируют повторно используемую реактивную логику и могут быть легко подключены в компоненты Vue. Этот подход является ключевой частью Composition API и позволяет строить масштабируемые, модульные и тестируемые приложения.


Основы composables

Composables — это обычные функции JavaScript, в которых используются реактивные переменные (ref, reactive) и вычисляемые свойства (computed). Их основная цель — отделение логики от компонентов и повторное использование кода.

Пример простого composable:

import { ref, computed } from 'vue';

export function useCounter() {
  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  const double = computed(() => count.value * 2);

  return {
    count,
    increment,
    decrement,
    double
  };
}

В этом примере useCounter инкапсулирует логику счетчика. Любой компонент может подключить этот composable и использовать его состояние и методы без дублирования кода.


Принципы проектирования composables

  1. Одно назначение Каждый composable должен решать конкретную задачу. Например, useFetch для запросов к API или useFormValidation для проверки форм.

  2. Возвращение только нужного Не следует возвращать лишние переменные или методы. Только те, которые необходимы для работы компонента.

  3. Не зависеть от компонента Composable должен быть независимым. Он не должен напрямую обращаться к DOM или жизненному циклу компонента, за исключением хуков Composition API (onMounted, onUnmounted и т.д.).

  4. Использование реактивности Основная сила composables заключается в возможности использовать реактивные переменные и вычисляемые свойства для синхронизации состояния.


Примеры сложных composables

1. Composable для работы с API

import { ref } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);

  const fetchData = async () => {
    loading.value = true;
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Ошибка сети');
      data.value = await response.json();
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };

  return {
    data,
    error,
    loading,
    fetchData
  };
}

Этот composable можно использовать в любом компоненте для выполнения запросов и управления состоянием загрузки и ошибок.

2. Composable для локального хранилища

import { ref, watch } from 'vue';

export function useLocalStorage(key, initialValue) {
  const storedValue = localStorage.getItem(key);
  const data = ref(storedValue ? JSON.parse(storedValue) : initialValue);

  watch(data, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue));
  }, { deep: true });

  return data;
}

Этот подход позволяет автоматически синхронизировать реактивные переменные с localStorage.


Работа с жизненным циклом компонента

Composables могут использовать хуки жизненного цикла для выполнения действий на определённых этапах существования компонента:

import { onMounted, onUnmounted, ref } from 'vue';

export function useWindowWidth() {
  const width = ref(window.innerWidth);

  const updateWidth = () => {
    width.value = window.innerWidth;
  };

  onMounted(() => {
    window.addEventListener('resize', updateWidth);
  });

  onUnmounted(() => {
    window.removeEventListener('resize', updateWidth);
  });

  return width;
}

Использование таких composables позволяет компоненту оставаться чистым и сосредоточенным на отображении данных.


Организация и структура composables

  1. Папка composables Все composables рекомендуется хранить в отдельной папке проекта, например, src/composables.

  2. Именование По соглашению функции composables начинаются с префикса use, например: useAuth, useTheme, useForm.

  3. Разделение логики Если функционал становится слишком большим, его стоит разделять на несколько composables для повышения читаемости и тестируемости.


Интеграция с Vuex и Pinia

Composables отлично сочетаются с глобальными хранилищами состояния. Вместо прямого подключения состояния в компонент, можно создать composable, который оборачивает работу со стором:

import { computed } from 'vue';
import { useStore } from 'vuex';

export function useCart() {
  const store = useStore();

  const items = computed(() => store.state.cart.items);
  const total = computed(() => store.getters['cart/totalPrice']);

  const addItem = (product) => {
    store.commit('cart/addItem', product);
  };

  return { items, total, addItem };
}

Такой подход упрощает тестирование и повторное использование бизнес-логики.


Важные рекомендации

  • Composables не должны содержать визуальные элементы или шаблоны. Они только управляют состоянием и логикой.
  • Можно комбинировать несколько composables внутри одного компонента для построения сложного поведения.
  • Тестирование composables проще, чем компонентов, так как они не зависят от DOM.

Использование composables позволяет создавать приложения, где логика четко разделена, компоненты остаются компактными, а код легко поддерживается и расширяется.