Вы уже наверняка заметили, что в разработке бывают моменты, когда какой-то код хочется использовать не один раз. Например, у нас может быть логика для работы с API, валидация данных или какая-нибудь хитрая работа с состоянием. Копировать её в каждый компонент — не лучший вариант. В таких случаях на помощь приходят composables — мощный инструмент для переиспользования логики в Vue 3. И если раньше вы пользовались миксинами, то готовьтесь к приятному обновлению, потому что composables делают всё проще и нагляднее.

Что такое Composables?

Composables — это функции, которые позволяют повторно использовать код, не зависящий от конкретных компонентов. Они работают по принципу «вытащи логику наружу», что позволяет вам выносить общие куски кода в отдельные модули и использовать их в нескольких местах приложения.

Иными словами, это те же самые обычные функции, но с возможностью легко взаимодействовать с реактивностью Vue.

Почему стоит использовать Composables?

  • Чистота кода: Вынеся логику в composables, ваш код в компонентах станет легче читать и поддерживать.
  • Повторное использование: Один раз написали — много раз использовали.
  • Гибкость: Composables можно вызывать и использовать в любых компонентах, а не только в тех, где был вызван определённый миксин или хук.
  • Удобная реактивность: С Vue 3 и Composition API управлять состоянием и реактивностью через composables стало ещё проще.

Пример базового Composable

Представим, что у нас есть простая задача — создать счётчик. Ранее мы уже видели это в рамках компонента, но что если нужно использовать счётчик в нескольких местах? Давайте вынесем эту логику в composable.

Создадим файл useCounter.js в папке composables:

import { ref } from 'vue';

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

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

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

  return { count, increment, decrement };
}

Здесь всё просто: мы создаём функцию useCounter, внутри которой используется ref для создания реактивной переменной count. Функции increment и decrement изменяют это состояние. В конце возвращаем всё это наружу, чтобы использовать в компонентах.

Теперь давайте подключим наш composable в компонент:

<template>
  <div>
    <h1>Счётчик: {{ count }}</h1>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
import { useCounter } from './composables/useCounter';

export default {
  setup() {
    const { count, increment, decrement } = useCounter();
    
    return { count, increment, decrement };
  }
};
</script>

Вот и всё! Мы вынесли логику счётчика в отдельную функцию useCounter и теперь можем подключить её в любом компоненте, где это нужно. Больше не нужно дублировать код — просто вызываем функцию и работаем с результатом.

Пример более сложного Composable: работа с API

Давайте посмотрим на более жизненный пример, когда нужно выполнять асинхронные запросы к API. Мы можем создать composable для работы с сервером, например, для получения списка пользователей.

Создадим useUsers.js:

import { ref, onMounted } from 'vue';

export function useUsers() {
  const users = ref([]);
  const isLoading = ref(false);
  const error = ref(null);

  async function fetchUsers() {
    isLoading.value = true;
    error.value = null;
    
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      users.value = await response.json();
    } catch (err) {
      error.value = 'Ошибка при загрузке пользователей';
    } finally {
      isLoading.value = false;
    }
  }

  onMounted(fetchUsers);

  return { users, isLoading, error, fetchUsers };
}

Здесь мы создали функцию useUsers, которая управляет состоянием пользователей, индикатором загрузки и ошибками. Когда компонент, использующий этот composable, монтируется, автоматически вызывается функция fetchUsers, которая делает запрос к API и сохраняет данные в реактивное состояние users.

Теперь подключим это в компонент:

<template>
  <div>
    <div v-if="isLoading">Загрузка...</div>
    <div v-else-if="error">{{ error }}</div>
    <ul v-else>
      <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script>
import { useUsers } from './composables/useUsers';

export default {
  setup() {
    const { users, isLoading, error } = useUsers();
    
    return { users, isLoading, error };
  }
};
</script>

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

Лучшая организация кода с Composables

Composables помогают улучшить структуру кода в больших проектах. Когда у вас много логики, которую нужно использовать в нескольких местах, вы можете разделить её на отдельные composables по функционалу:

  • Работа с API: useApi
  • Управление формами: useForm
  • Управление модальными окнами: useModal
  • Валидация данных: useValidation

Каждый из этих composables может быть чистой и независимой функцией, которую можно использовать там, где она необходима.

Как работают реактивные данные внутри Composables?

Composables — это обычные функции JavaScript, которые используют реактивность Vue для управления состоянием. Когда мы создаём реактивные данные через ref или reactive, Vue отслеживает изменения этих данных. Даже если мы вынесем эти данные в отдельную функцию, реактивность сохраняется.

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

А что с TypeScript?

Composables прекрасно работает с TypeScript, и если вы используете этот язык, то с composables работать ещё приятнее. Вы можете типизировать состояния, методы и возвращаемые значения, что значительно упрощает разработку и делает код надёжнее.

Пример типизированного composable:

import { ref } from 'vue';

interface User {
  id: number;
  name: string;
}

export function useTypedUsers() {
  const users = ref<User[]>([]);

  function addUser(user: User) {
    users.value.push(user);
  }

  return { users, addUser };
}

 

Дальше мы рассмотрим тему маршрутизация с Vue Router.

 

Один комментарий