Сравнение Vue 3 Options API и Composition API

Сравнение Vue 3 Options API и Composition API

Одним из важнейших аспектов любого фронтенд-фреймворка является способ создания компонентов. Интересно, что Vue.js предлагает не один, а два метода для этого процесса - Options API и Composition API. Но полезно ли это для разработчиков или нет? Давайте выясним.

В этой статье мы сравним каждый из вариантов создания компонентов и обсудим, как выбрать лучший метод для ваших нужд. Мы рассмотрим:

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

Почему Vue.js предоставляет два API для создания компонентов?

Вы можете задаться вопросом, почему Vue предоставляет два способа создания компонентов и нужны ли они оба. Чтобы ответить на этот вопрос, необходимо обратиться к истокам создания Vue.

Vue возникла как простая и удобная библиотека в противовес сложности Angular и неорганизованности React. Цель Vue заключалась в том, чтобы предложить разработчикам простой способ быстро и легко создавать приложения или добавлять интерактивность в существующие проекты или сайты.

API Options успешно достигает этой цели, предоставляя четкий и понятный способ описания компонента путем группировки его кода в опции. При этом не требуется глубоких размышлений или кодовой инженерии - достаточно поместить код в группу опций.

Эта простота - одна из основных причин, по которой разработчики так любят Vue. Однако по мере того как Vue набирал обороты, он превращался в серьезный фреймворк для создания более сложных и требовательных проектов.

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

Composition API был разработан для устранения ограничений Options API, в частности, его организации кода. В более сложных приложениях Options API может привести к ”спагетти” кода и дефрагментации. Приведенное ниже изображение иллюстрирует эти различия:

Цветовое разбиение организации кода с помощью Composition Api и Options Api. Группировка кода в опции с помощью Options Api приводит к дефрагментации кода, в то время как Composition Api улучшает целостность кода

При использовании Options API мы часто разделяем целостную единицу кода на несколько частей и помещаем их в соответствующие группы опций. Для небольших и простых компонентов такое расположение не представляет особой проблемы.

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

Как мы уже видели, две стратегии создания компонентов в Vue развивались естественным образом по мере развития языка. Не позволяйте этому сбить вас с толку!

В оставшейся части статьи мы более подробно рассмотрим оба API, их преимущества и недостатки. Надеемся, что это прояснит ваше понимание и поможет выбрать правильный API для ваших конкретных сценариев.

Сравнение компонента Vue.js, созданного с помощью API Options, с API Composition

Если вы новичок в Vue, то приведенные выше объяснения могут показаться вам несколько абстрактными. Поэтому давайте немного конкретизируем ситуацию. Сначала рассмотрим, как выглядит структура Options API:

export default {
  data() {
    return {

    }
  },

  computed: {

  },

  methods: {

  }
}

В приведенном примере data, computed и methods - это так называемые опции:

  • в данных содержатся все переменные
  • computed содержит любые вычисляемые переменные
  • методы содержат все необходимые функции приложения

Существуют и другие возможности, но здесь мы покажем только основные из них.

Давайте посмотрим на это в действии на примере простого приложения To-Do List:

<template>
  <h3> My To Do List </h3>
  <div>
    <input v-model="newItemText" v-on:keyup.enter="addNewTodo" />
    <button v-on:click="addNewTodo">Add</button>
    <button v-on:click="removeTodo">Remove</button>
    <button v-on:click="removeAllTodos">Remove All</button>
    <transition-group name="list" tag="ul">
      <li v-for="task in tasks" v-bind:key="task" >{{ task }}</li>
    </transition-group>
  </div>
</template>

<script>
  export default {
    data() { 
      return {
        tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
        newItemText: ""
      }
    },
    methods: {
      addNewTodo() {
        if (this.newItemText != "") {
          this.tasks.unshift(this.newItemText)
        }
        this.newItemText = ""
      },
      removeTodo() {
        this.tasks.pop()
      },
      removeAllTodos() {
        this.tasks = []
      }
    }
  }
</script>

<style>
  button {
    margin: 5px;
  }

  ul {
    margin: 30px 0 0 0;
    padding: 0;
    text-align: left;
  }

  li {
    font-size: 1.2em;
    list-style: none;
  }

  .list-enter-active {
    animation: add-item 1s;
  }

  .list-leave-active {
    position: absolute;
    animation: add-item 1s reverse;
  }

  .list-move {
    transition: transform 1s;
  }

  @keyframes add-item {
    0% {
      opacity: 0;
      transform: translateX(150px);
    }
    50% {
      opacity: 0.5;
      transform: translateX(-10px) skewX(20deg);
    }
    100% {
      opacity: 1;
      transform: translateX(0px);
    }
  }
</style>

Приведенный пример содержит две переменные и три функции для добавления и удаления пунктов дел. Все они используются в шаблоне.

Как видите, организация кода довольно простая - хорошо организованная и понятная. Это объясняется тем, что наш пример очень прост, но для более сложного компонента это может быть не так.

Обратите внимание, что я не буду объяснять здесь CSS-код. Он приведен для полноты картины и для улучшения пользовательского интерфейса в нашем примере.

Для сложного компонента лучше использовать API Composition. Вот краткая демонстрация структуры этого API:

export default {
  setup() {
    // Composition API code
    return {
      // Composition API for use in the template
    }
  }
}

API Composition используется в функции setup(), которая, что интересно, является еще одним вариантом API Options 🙂 . Эта функция позволяет нам использовать реактивность Vue 3 для создания реактивных переменных.

Давайте посмотрим на это в действии на примере, который повторяет наш предыдущий пример с Options API:

<script>
  import { ref } from 'vue'

  export default {
    setup() {
      const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
      const newItemText = ref("")

      function addNewTodo() {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value)
        }
        newItemText.value = ""
      }
      function removeTodo() {
        tasks.value.pop()
      }
      function removeAllTodos() {
        tasks.value = []
      }

      return {
        tasks,
        newItemText,
        addNewTodo,
        removeTodo,
        removeAllTodos
      }
    }
  }
</script>

В данном примере мы используем функцию ref для объявления реактивных переменных. Затем мы создаем необходимые функции. Наконец, мы возвращаем все переменные и функции, которые хотим использовать в шаблоне.

Сравнение возможности повторного использования Composition API и Options API

Как я уже говорил, одной из основных причин создания Composition API было повышение возможности повторного использования. Vue достигает этого путем введения нового типа компонентов, называемых композитами (composables).

Составные компоненты - это фрагменты кода, которые можно использовать многократно в одном приложении и совместно использовать в разных проектах. В API Options подобную функциональность предоставляют миксины, но они имеют ряд недостатков по сравнению с композитами. Рассмотрим некоторые примеры.

Допустим, нам нужна функциональность счетчика, которую мы хотим повторно использовать в компонентах нашего приложения. Вот как мы можем создать его с помощью миксина:

// CounterMixin.js
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    set(val) {
      this.count = val
    },
    reset() {
      this.count = 0
    }
  }
}

Как видно, миксин - это просто объект опций. И, как мы увидим в следующем примере, мы можем внедрить этот объект в наш компонент.

Продолжая пример со списком дел, изменим заголовок, чтобы он содержал переменную count:

<h3> My To Do List ({{ count }}) </h3>

Затем замените код в секции script на следующий:

<script>
  import CounterMixin from './mixins/CounterMixin.js'

  export default {
    mixins: [CounterMixin],
    data() { 
      return {
        tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
        newItemText: ""
    }},
    mounted() {
      this.set(this.tasks.length)
    },
    methods: {
      addNewTodo() {
        if (this.newItemText != "") {
          this.tasks.unshift(this.newItemText)
          this.increment()
        }
        this.newItemText = ""
      },
      removeTodo() {
        this.tasks.pop()
        this.decrement()
      },
      removeAllTodos() {
        this.tasks = []
        this.reset()
      }
    }
  }
</script>

Здесь мы импортируем миксин, а затем регистрируем его с помощью опции mixins. После этого все свойства объекта mixins объединяются с объектом экземпляра Vue.

Теперь мы можем использовать переменные и функции из CounterMixin. С помощью хука mounted() мы устанавливаем начальный счетчик для задач.

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

  • Миксины не могут принимать параметры, поэтому мы не можем использовать динамическую логику. Это делает их менее гибкими и многократно используемыми по сравнению с композитами
  • Данные в миксинах не защищены от мутаций. Потребляющий компонент может "молча" изменить свойство миксина, что может привести к трудно выявляемым ошибкам
  • При использовании миксинов мы не можем однозначно отследить данные компонента до их источника. Это справедливо как для локально, так и для глобально зарегистрированных миксинов
  • При использовании нескольких миксинов могут возникать конфликты имен. Как и в случае с правилом "побеждает последний" в CSS, если у вас есть два свойства с одинаковыми именами из разных миксинов, то победит свойство из последнего миксина.

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

// useCounter.js
import { ref, readonly } from 'vue'

export default function useCounter() {
  const count = ref(0)
  const increment = () => { count.value += 1 }
  const decrement = () => { count.value -= 1 }
  const set = (val) => { count.value = val }
  const reset = () => { count.value = 0 }

  return {
    count: readonly(count),
    increment,
    decrement,
    set,
    reset
  }
}

Композитные функции создаются по тем же принципам, что и функция setup().

Мы создаем необходимые переменные и функции внутри экспортируемой композитной функции и возвращаем те части, которые мы хотим использовать в дальнейшем. Мы также используем функцию readonly() из Reactivity API, чтобы переменная count не была изменена потребляющим компонентом.

Теперь посмотрим, как применяется этот композит. Заменим содержимое секции скрипта на следующее:

<script>
  import { ref, onMounted } from 'vue'
  import useCounter from './composables/useCounter.js'

  export default {
    setup() {
      const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
      const newItemText = ref("")

      const {count, increment, decrement, set, reset} = useCounter()
      onMounted(() => set(tasks.value.length))

      function addNewTodo() {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value)
        }
        newItemText.value = ""
        increment()
      }
      function removeTodo() {
        tasks.value.pop()
        decrement()
      }
      function removeAllTodos() {
        tasks.value = []
        reset()
      }

      return {
        count,
        tasks,
        newItemText,
        addNewTodo,
        removeTodo,
        removeAllTodos
      }
    }
  }
</script>

Здесь мы импортируем компонент, а затем извлекаем его переменные и функции, используя синтаксис деструктуризации присваивания. Мы используем хук onMounted() для установки начального количества дел.

Как видите, здесь хорошо видно, откуда взялись переменные и функции счетчика и какие именно из них доступны для использования.

По сравнению с миксинами, композиты имеют ряд преимуществ:

  • В composables источник данных для каждого свойства или метода ясен и прослеживается. Мы можем видеть, откуда они импортируются и какие именно свойства или методы мы используем.
  • При использовании нескольких составных таблиц можно легко избежать конфликтов имен, переименовывая свойства с одинаковыми именами
  • Составные компоненты позволяют использовать свойства, доступные только для чтения, что позволяет избежать случайных мутаций, исходящих от других компонентов, и тем самым сохранить данные в безопасности
  • Составные элементы создают новое локальное состояние для каждого компонента, в котором они используются, но они также могут определять глобальное, общее состояние

Хорошей новостью является то, что вы можете использовать композиты вместе с API Options. Если вам нужны профессионально созданные и готовые к использованию композиты, я рекомендую обратить внимание на замечательную коллекцию под названием VueUse.

Выбор между Composition API и Options API для проекта Vue.js

Итак, мы убедились, что и Composition API, и Options API являются отличными инструментами со своими преимуществами и недостатками. Так какой же из них выбрать для своего проекта?

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

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

Ознакомьтесь с краткой таблицей сравнения Vue Composition API и Vue Options API, которая поможет вам определиться с выбором:

Заключение

В этой статье мы рассмотрели два способа создания компонентов, предоставляемых Vue, - API Options и API Composition. Как мы убедились, оба способа обеспечивают отличную функциональность, но для разных сценариев.

Основной вывод — принять правильное решение о том, какой из них лучше всего подходит для конкретного проекта, и затем наслаждаться его созданием. Если у вас возникли дополнительные вопросы, не стесняйтесь оставлять комментарии ниже.