Одним из важнейших аспектов любого фронтенд-фреймворка является способ создания компонентов. Интересно, что 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 может привести к ”спагетти” кода и дефрагментации. Приведенное ниже изображение иллюстрирует эти различия:

При использовании 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. Как мы убедились, оба способа обеспечивают отличную функциональность, но для разных сценариев.
Основной вывод — принять правильное решение о том, какой из них лучше всего подходит для конкретного проекта, и затем наслаждаться его созданием. Если у вас возникли дополнительные вопросы, не стесняйтесь оставлять комментарии ниже.