Composition API и TypeScript

Composition API — современный способ организации логики во Vue.js, позволяющий создавать более модульный и переиспользуемый код. В отличие от Options API, где данные, методы и жизненные циклы компонента разнесены по разным опциям, Composition API концентрирует связанную логику вместе, используя функции и реактивные примитивы.

Основные примитивы Composition API:

  • ref() — создание реактивного примитива для простых значений.
  • reactive() — создание реактивного объекта.
  • computed() — создание вычисляемого свойства, автоматически отслеживающего зависимости.
  • watch() и watchEffect() — слежение за изменениями данных.

Пример базового использования:

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

export default {
  setup() {
    const count = ref(0);
    const state = reactive({ message: 'Hello Vue!' });

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

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

    return { count, state, doubled, increment };
  }
};

Работа с реактивностью

Реактивность в Composition API строится на принципе “обёртывания значений”.

  • ref подходит для примитивов: чисел, строк, булевых значений.
  • reactive используется для объектов и массивов.

Важно помнить, что доступ к значению ref осуществляется через .value.

const name = ref('Vue');
console.log(name.value); // Vue
name.value = 'Vue 3';

Для объектов, созданных через reactive, доступ осуществляется напрямую без .value:

const user = reactive({ name: 'Alice', age: 25 });
console.log(user.name); // Alice
user.age++;

Вычисляемые свойства и слежение

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

const count = ref(2);
const squared = computed(() => count.value ** 2);
console.log(squared.value); // 4

watch отслеживает изменения конкретных реактивных значений и выполняет побочные эффекты:

watch(count, (newVal, oldVal) => {
  console.log(`Count изменился с ${oldVal} на ${newVal}`);
});

watchEffect позволяет автоматически отслеживать все используемые внутри него реактивные данные:

watchEffect(() => {
  console.log(`Текущее значение count: ${count.value}`);
});

Интеграция с TypeScript

Composition API идеально сочетается с TypeScript, так как реактивные примитивы и функции предоставляют строгую типизацию.

Примеры типизации:

import { ref, reactive } from 'vue';

const count = ref<number>(0); // ref с числовым типом
const user = reactive<{ name: string; age: number }>({
  name: 'Alice',
  age: 25
});

Типизация computed:

import { computed } from 'vue';

const count = ref(5);
const doubled = computed<number>(() => count.value * 2);

Для функций внутри setup можно явно указывать типы аргументов и возвращаемого значения:

function increment(value: number): number {
  return value + 1;
}

Организация кода с Composition API

Логика, связанная с конкретной функциональностью, часто выносится в composable — отдельные функции, которые возвращают реактивные данные и методы. Это позволяет переиспользовать код в разных компонентах.

Пример composable:

import { ref } from 'vue';

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

  const increment = () => count.value++;
  const decrement = () => count.value--;

  return { count, increment, decrement };
}

Использование в компоненте:

import { useCounter } from './useCounter';

export default {
  setup() {
    const { count, increment } = useCounter(10);
    return { count, increment };
  }
};

Жизненный цикл компонентов

Composition API предоставляет функции для работы с жизненным циклом:

  • onMounted() — вызывается после монтирования компонента.
  • onUpdated() — вызывается после обновления компонента.
  • onUnmounted() — вызывается перед удалением компонента.

Пример:

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

export default {
  setup() {
    const time = ref(0);
    let timer: number;

    onMounted(() => {
      timer = window.setInterval(() => time.value++, 1000);
    });

    onUnmounted(() => {
      clearInterval(timer);
    });

    return { time };
  }
};

Взаимодействие с пропсами и эмитами

В Composition API доступ к пропсам осуществляется через параметр setup(props, context):

export default {
  props: {
    title: String
  },
  setup(props, { emit }) {
    const changeTitle = () => emit('update:title', 'New Title');
    console.log(props.title);
    return { changeTitle };
  }
};

Пропсы автоматически типизируются в TypeScript при использовании интерфейсов:

interface Props {
  title: string;
}

export default {
  props: {
    title: String
  },
  setup(props: Props) {
    console.log(props.title);
  }
};

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

Composition API легко интегрируется с современными библиотеками управления состоянием и маршрутизации.

  • Для Vue Router используется useRouter() и useRoute():
import { useRouter, useRoute } from 'vue-router';

export default {
  setup() {
    const router = useRouter();
    const route = useRoute();

    const goHome = () => router.push('/');
    console.log(route.params.id);

    return { goHome };
  }
};
  • Для Pinia используется defineStore и реактивные состояния напрямую в setup:
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({ name: 'Alice', age: 25 }),
  actions: {
    setName(newName: string) {
      this.name = newName;
    }
  }
});

Composition API делает работу с состоянием и маршрутами более удобной и типобезопасной.

Советы по организации больших проектов

  1. Выносить повторяющуюся логику в composables.
  2. Использовать строгую типизацию TypeScript для всех реактивных примитивов.
  3. Разделять бизнес-логику и представление, минимизируя дублирование кода.
  4. Группировать связанные реактивные данные и методы внутри setup для удобства чтения.
  5. Использовать watch и watchEffect для побочных эффектов вместо мутаций данных напрямую.

Composition API совместно с TypeScript позволяет создавать масштабируемые, поддерживаемые и безопасные приложения на Vue.js.