Кодирование лучших составных частей (1/5)

Composables — это, безусловно, лучший способ организовать бизнес-логику в вашем приложении Vue 3.

Автор: Майкл Тиссен

Composables — это, безусловно, лучший способ организовать бизнес-логику в вашем приложении Vue 3.

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

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

Вот что мы рассмотрим:

  1. Как использовать параметр объекта параметров, чтобы сделать ваши компонуемые объекты более настраиваемыми 👈 мы здесь
  2. Использование ref и unref, чтобы сделать наши аргументы более гибкими
  3. Простой способ сделать возвращаемые значения более полезными
  4. Почему, начиная с интерфейса, ваши компонуемые компоненты становятся более надежными
  5. Как использовать асинхронный код без необходимости await — сделать ваш код более понятным

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

Что такое компонуемый?

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

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

Вот простой пример компоновки useMouse из документации Vue.js:

import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
  const x = ref(0)
  const y = ref(0)
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  return { x, y }
}

Мы определяем наше состояние как refs, а затем обновляем это состояние при каждом движении мыши. Возвращая ссылки x и y, мы можем использовать их внутри любого компонента (или даже другого компонуемого).

Вот как мы будем использовать этот компонуемый внутри компонента:

<template>
  X: {{ x }} Y: {{ y }}
</template>
<script setup>
  import { useMouse } from './useMouse';
  const { x, y } = useMouse();
</script>

Как видите, использование компонуемого useMouse позволяет нам легко повторно использовать всю эту логику. С очень небольшим количеством дополнительного кода мы можем получить координаты мыши в нашем компоненте.

Теперь, когда мы находимся на одной странице, давайте посмотрим на первый шаблон, который поможет нам писать лучшие компонуемые объекты.

Параметр объекта Options

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

При настройке вашего компонуемого вместо передачи длинного списка аргументов вы можете вместо этого передать объект параметров:

// Using an options object
const title = useTitle('A new title', { titleTemplate: '>> %s <<' });
// Title is now ">> A new title <<"
// Using more arguments
const title = useTitle('A new title', '>> %s <<');
// Title is now ">> A new title <<"

Передача параметров как целого объекта вместо аргументов дает нам несколько преимуществ:

Во-первых, нам не нужно запоминать правильный порядок аргументов. Теперь с TypeScript и отличным автозаполнением в наших редакторах это не так важно, но все равно имеет значение. Для объекта Javascript порядок ключей не имеет значения.

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

В-третьих, впоследствии намного добавить новые параметры проще. Это относится как к добавлению новых опций в сам компонуемый, так и к добавлению опций при использовании компонуемого.

Таким образом, лучше использовать объект параметров. Но как мы это реализуем?

Реализация в составном

Вот как вы могли бы реализовать шаблон объекта параметров в составном объекте:

export function useMouse(options) {
  const {
    asArray = false,
    throttle = false,
  } = options;
  // ...
};

Здесь мы можем принять один или два аргумента, если они необходимы, а затем последний аргумент — это объект параметров. Итак, в этом примере useMouse не имеет обязательных аргументов, только объект параметров.

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

Теперь мы посмотрим, как два разных составных объекта из VueUse применяют этот шаблон. VueUse — это коллекция компоновок с открытым исходным кодом для Vue 3, и она очень хорошо написана. Это отличный ресурс, чтобы научиться писать великолепные составные части!

Сначала мы рассмотрим useTitle, а затем посмотрим, как работает useRefHistory.

useTitle

Компоновка useTitle довольно проста. Это позволяет вам обновить заголовок страницы:

const title = useTitle('Initial Page Title');
// Title: "Initial Page Title"
title.value = 'New Page Title';
// Title: "New Page Title"

Он также имеет несколько опций для дополнительной гибкости.

Вы можете указать titleTemplate, а также установить observe заголовок для любых изменений, которые могут быть сделаны другими скриптами (используя MutationObserver):

const titleOptions = {
  titleTemplate: '>> %s <<',
  observe: true,
};

Вот как можно использовать объект options:

const title = useTitle('Initial Page Title', {
  titleTemplate: '>> %s <<',
  observe: true,
});
// Title: ">> Initial Page Title <<"
title.value = 'New Page Title';
// Title: ">> New Page Title <<"

Когда вы посмотрите на исходный код для useTitle, вы увидите, как это делается:

export function useTitle(newTitle, options) {
  const {
    document = defaultDocument,
    observe = false,
    titleTemplate = '%s',
  } = options;
  
  // ...
}

Составной объект useTitle имеет один обязательный аргумент, а затем объект options. После этого он реализует остальную часть шаблона точно так, как описано здесь.

Теперь давайте рассмотрим немного более сложный компонуемый объект, который также использует этот шаблон объекта параметров.

useRefHistory

Компонуемый useRefHistory немного интереснее. Это позволяет отслеживать все изменения, внесенные в ref, что позволяет довольно легко выполнять операции отмены и повтора:

// Set up the count ref and track it
const count = ref(0);
const { undo } = useRefHistory(count);
// Increment the count
count.value++;
// Log out the count, undo, and log again
console.log(counter.value); // 1
undo();
console.log(counter.value); // 0

Этот компонуемый может принимать множество различных вариантов:

{
  deep: false,
  flush: 'pre',
  capacity: -1,
  clone: false,
  // ...
}

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

Мы можем передать объект параметров в качестве второго параметра для дальнейшей настройки поведения этого составного объекта, как и в нашем предыдущем примере:

const state = ref({});
const { undo, redo } = useRefHistory(state, {
  deep: true,    // Track changes inside of objects and arrays
  capacity: 15,  // Limit how many steps we track
});

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

export function useRefHistory(source, options) {
  const {
    deep = false,
    flush = 'pre',
    eventFilter,
  } = options;
 
  // ...
}

Однако в этом примере мы извлекаем только несколько значений из объекта параметров здесь, в начале.

Это связано с тем, что useRefHistory полагается на компонуемый useManualRefHistory внутри. Остальные параметры передаются как объект опций этого компонуемого позже в компонуемом:

// ...
const manualHistory = useManualRefHistory(
  source,
  {
    // Pass along the options object to another composable
    ...options,
    clone: options.clone || deep,
    setSource,
  },
);
// ...

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

Объединяя все это

Эта статья была первой частью нашей серии «Написание лучших компонуемых».

Мы рассмотрели, как добавление объекта параметров в качестве параметра может сделать компоненты более настраиваемыми. Например, вам не нужно беспокоиться о порядке аргументов или помнить, что делает каждый аргумент, а добавить дополнительные параметры к объекту гораздо проще, чем обновить переданные аргументы.

Но мы не просто смотрели на сам узор. Мы также видели, как компонуемые файлы VueUse useTitle и useRefHistory реализуют этот шаблон. Они делают это немного по-разному, но, поскольку это простой шаблон, вы не можете сделать много вариаций.

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

// Works if we give it a ref we already have
const countRef = ref(2);
useCount(countRef);
// Also works if we give it just a number
const countRef = useRef(2);

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

Вы дошли до конца этой статьи, так почему бы не перейти к ней и не продолжить изучение передового опыта компоновки?

Первоначально опубликовано на https://www.vuemastery.com 18 апреля 2022 г.