Практическое введение в Composition API Vue 3

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

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

Хотя решения для выделения общих элементов, такие как миксин (mixin), компонент без рендеринга (renderless compontent) или HOC, существовали и прежде, ни одно из них не могло обеспечить столь высокую степень гибкости, как Composition API. Основным преимуществом этой новой функциональности является возможность одновременного извлечения элементов, которые прежде приходилось реализовывать для определенного компонента в данных, вычисляемых свойствах (computed) и методах (с помощью Options API). Рассмотрим краткий пример использования Composition API в реальных ситуациях.

В качестве первого шага создайте проект с помощью Vue.js (версия 3). Ради скорости я буду использовать для этой цели Vue Cli. По завершении проекта удалите компонент примера, он нам больше не понадобится. Уточните структуру проекта, в которую мы организуем код. Я помещаю свои компоненты в папку component под Src и одновременно создаю папку services, в которую упаковываю повторно используемый, но “бескомпонентный” код, реализующий бизнес-логику. Один файл содержит одну службу, например реализующую различные сортировки и фильтрации или http-вызовы.

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

Composition API вводит новую часть в код компонента, называемую setup. Это функция, которая может иметь 2 параметра: контекст (context) и свойства (props). Внутри setup мы можем реализовать сегменты, которые написали в разделах данных, вычисляемых свойств (computed) и методов, используя известный ранее API Options. Здесь мы можем определить внутреннее состояние и функции компонента, а также здесь использовать общие фрагменты кода, которые я называю службами (services).

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

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

Компонент агрегации приложения HomePage отображает только две таблицы: в одной перечислены пользователи, а в другой  —  продукты. Composition API позволяет создавать операцию сортировки как отдельную службу, где при необходимости можно работать с реактивными значениями, которые затем можно сделать доступными в компонентах, использующих эту службу.

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

Конечный результат

Результат: две разные таблицы используют один и тот же сервис для сортировки данных

Компонент HomePage содержит две таблицы (и несколько строк кода css для выделения таблиц):

1  <template>
2      <ProductTable />
3      <UserTable />
4  </template>
5
6  <script>
7  import ProductTable from '../tables/ProductTable';
8  import UserTable from '../tables/UserTable';
9
10 export default {
11   name: 'HomePage',
12   components: {
13       ProductTable,
14       UserTable
15   }
16 }
17 </script>
18
19 <style>
20    table {
21       border: 1px solid rgb(13, 58, 126);
22     }
23
24     th {
25       background: rgb(183, 202, 245);
26       border: 1px solid black;
27       padding: 4px 10px 4px 10px;
28     }
29
30     td {
31       border: 1px solid rgb(185, 221, 245);
32       padding: 4px;
33     }
34
35     button {
36       background: rgb(183, 202, 245);
37       border: 1px solid #999;
38       padding: 4px;
39       font-size: 0.9rem;
40       margin-top: 10px;
41       margin-bottom: 10px;
42     }
43
44     button:hover {
45       cursor: pointer;
46       background: rgb(102, 138, 221);
47     }
48 </style>

Компоненты ProductTable и UserTable

Компонент ProductTable показывает простую таблицу с 3 столбцами и кнопкой для сортировки внизу. Он определяет строки данных для простоты из строки 25.

Строка 33: здесь используется служба сортировки (order service), чтобы получить возвращенные значения  —  orderState и orderItems. OrderState  —  это внутреннее состояние службы сортировки, содержащее значение направления («asc» или «desc»), а OrderItems  —  это сама функция сортировки. Как видите, вся логика сортировки отделена от компонента, использующего ее. Представьте, насколько полезным может быть это решение с 10 или более службами в одном компоненте.

Строка 35: выполняется сортировка с этой службой “во время загрузки” компонента (см. методы жизненного цикла vue 3).

Строка 37: возврат с объектом содержит state, orderItems (функция сортировки) и orderState (собственное состояние службы сортировки с параметром направления). Это важный момент, потому что Composition API нуждается в такого типа возврате для корректной работы. После возврата с этими атрибутами, шаблон компонента может использовать orderItems и orderState.direction, как это видно в строке 14.

Строка 8: это состояние может быть использовано в шаблоне непосредственно.

1  <template>
2      <table>
3          <tr>
4              <th>id</th>
5              <th>name</th>
6              <th>prize</th>
7          </tr>
8          <tr v-for="product in state.products" :key="product.id">
9              <td>{{ product.id }}</td>
10             <td>{{ product.name }}</td>
11             <td>{{ product.prize }}</td>
12         </tr>
13     </table>
14     <button @click="orderItems(state.products)">Order table ({{         orderState.direction }})</button>
15 </template>
16
17 <script>
18 import { reactive } from 'vue';
19 import order from '../../services/order-manual';
20
21 export default {
22   name: 'ProductTable',
23   setup() {
24     const state = reactive({
25         products: [
26             { id: 0, name: 'table', prize: '1000' },
27             { id: 1, name: 'chair', prize: '2000' },
28             { id: 2, name: 'pen', prize: '3000' },
29             { id: 3, name: 'notebook', prize: '4000' },
30         ]
31     });
32
33     const { orderItems, orderState } = order();
34
35     orderItems(state.products);
36
37     return {
38         state,
39         orderItems,
40         orderState
41     };
42   }
43 }
44 </script>

Компонент UserTable практически идентичен ранее проанализированному ProductTable. Единственное отличие  —  это ряды данных в строке 25. Этот компонент может воспользоваться службой сортировки точно так же, как и ProductTable.

1  <template>
2      <table>
3          <tr>
4              <th>id</th>
5              <th>name</th>
6              <th>email</th>
7          </tr>
8          <tr v-for="user in state.users" :key="user.id">
9              <td>{{ user.id }}</td>
10             <td>{{ user.name }}</td>
11             <td>{{ user.email }}</td>
12         </tr>
13     </table>
14     <button @click="orderItems(state.users)">Order table ({{  orderState.direction }})</button>
15 </template>
16
17 <script>
18 import { reactive } from 'vue';
19 import order from '../../services/order-manual';
20 
21 export default {
22   name: 'UserTable',
23   setup() {
24     const state = reactive({
25         users: [
26             { id: 0, name: 'John', email: '[email protected]' },
27             { id: 1, name: 'Peter', email: '[email protected]' },
28             { id: 2, name: 'Dora', email: '[email protected]' },
29             { id: 3, name: 'Ben', email: '[email protected]' },
30         ]
31     });
32 
33     const { orderItems, orderState } = order();
34
35     orderItems(state.users);
36
37     return {
38         state,
39         orderItems,
40         orderState
41     };
42   }
43 }
44 </script>

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

Помните: вы должны вернуть ВСЕ (функции, переменные и т. д.) из службы сортировки, чтобы сделать ее доступной на уровне компонента.

Главное преимущество Composition API заключается в том, что вся логика, связанная с функциональностью, может быть извлечена в отдельную часть, потому что вы можете “смоделировать” данные, метод и вычисляемые свойства Vue.js в одном единственном блоке кода. С этой идеей я могу создать и использовать таким же образом отдельную функциональность сортировки в любых компонентах.

1  import { reactive } from 'vue';
2
3  const order = () => {
4      let orderState = reactive({
5          direction: 'asc'
6  });
7
8      const orderItems = (items) => {
9          items.sort(compareItems);
10         changeOrderDirection();
11     }
12
13     const changeOrderDirection = () => {
14         if(orderState.direction === 'desc') {
15             orderState.direction = 'asc';
16         } else if (orderState.direction === 'asc') {
17             orderState.direction = 'desc';
18         }
19     }
20
21     const evaluateAscOrder = (itemName, otherItemName) => {
22         if(itemName < otherItemName) return 1;
23         if(itemName > otherItemName) return -1;
24        return 0;
25     }
26
27     const evaluateDescOrder = (itemName, otherItemName) => {
28         if(itemName > otherItemName) return 1;
29         if(itemName < otherItemName) return -1;
30         return 0;
31     }
32
33     const compareItems = (item, otherItem) => {
34         const itemName = item.name.toUpperCase();
35         const otherItemName = otherItem.name.toUpperCase();
36
37         if(orderState.direction === 'desc') {
38             return evaluateDescOrder(itemName, otherItemName);
39         } else {
40             return evaluateAscOrder(itemName, otherItemName);
41         }
42     }
43
44     return {
45         orderState,
46         orderItems
47     }
48 }
49 
50 export default order;

Заключение

Конечно, это очень простой пример. Но единственная цель этой статьи  —  привлечь ваше внимание к основному преимуществу Composition API. Если вы используете Vue.js для корпоративного ПО, попробуйте в следующий раз заменить обычный Options API и вы не будете разочарованы.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Dávid Csejtei: A Practical Intro to the Vue 3 Composition API