Skip to content

Composables во Vue 3

1. Что такое Composables?

Composables - это функции, которые инкапсулируют и переиспользуют логику состояния в Vue 3 компонентах.

2. Создание Composable

Базовый пример

typescript
// useCounter.ts
import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const double = computed(() => count.value * 2);

  function increment() {
    count.value++;
  }

  function decrement() {
    count.value--;
  }

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

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

Использование хуков

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

export function useMousePosition() {
  const x = ref(0);
  const y = ref(0);

  function update(e: MouseEvent) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => {
    window.addEventListener('mousemove', update);
  });

  onUnmounted(() => {
    window.removeEventListener('mousemove', update);
  });

  return { x, y };
}

4. Асинхронные Composables

Работа с API

typescript
import { ref } from 'vue';

export function useFetch<T>(url: string) {
  const data = ref<T | null>(null);
  const error = ref<Error | null>(null);
  const loading = ref(false);

  async function fetchData() {
    loading.value = true;
    try {
      const response = await fetch(url);
      data.value = await response.json();
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  }

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

5. Composables с состоянием

Управление состоянием

typescript
import { reactive, computed } from 'vue';

export function useCart() {
  const state = reactive({
    items: [] as Array<{ id: number; name: string; price: number }>
  });

  const total = computed(() => {
    return state.items.reduce((sum, item) => sum + item.price, 0);
  });

  function addItem(item: { id: number; name: string; price: number }) {
    state.items.push(item);
  }

  function removeItem(id: number) {
    const index = state.items.findIndex(item => item.id === id);
    if (index > -1) {
      state.items.splice(index, 1);
    }
  }

  return {
    items: state.items,
    total,
    addItem,
    removeItem
  };
}

6. Composables с параметрами

Настраиваемые Composables

typescript
import { ref, watch } from 'vue';

export function useStorage<T>(key: string, defaultValue: T) {
  const storedValue = ref<T>(
    JSON.parse(localStorage.getItem(key) ?? JSON.stringify(defaultValue))
  );

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

  return storedValue;
}

7. Комбинирование Composables

Создание сложных Composables

typescript
export function useUserProfile(userId: string) {
  const { data: user, loading: userLoading } = useFetch(`/api/users/${userId}`);
  const { data: posts, loading: postsLoading } = useFetch(`/api/users/${userId}/posts`);
  const { storedPreferences } = useStorage(`user-${userId}-prefs`, {});

  const isLoading = computed(() => userLoading.value || postsLoading.value);

  return {
    user,
    posts,
    storedPreferences,
    isLoading
  };
}

8. Лучшие практики

Правила и рекомендации

typescript
// ✅ Хорошо: Чёткое именование
export function useUserAuthentication() {
  // ...
}

// ✅ Хорошо: Возврат объекта с именованными значениями
export function useTimer() {
  const time = ref(0);
  return { time, start, stop, reset };
}

// ✅ Хорошо: Документация TypeScript
interface UseSearchOptions {
  immediate?: boolean;
  limit?: number;
}

export function useSearch(options?: UseSearchOptions) {
  // ...
}