Как создавать повторно используемые компоненты с помощью Vue 3 Composition API

Понимание новой системы реактивности в Vue 3 Изучение

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

Совместное использование кода и возможность повторного использования являются одними из краеугольных камней разработки программного обеспечения. С первых дней программирования проблема повторения кода заставляла программистов изобретать стратегии, позволяющие сохранять свой код СУХИМ, многоразовым и переносимым. Шло время, эти стратегии постоянно совершенствовались и совершенствовались, а новые постоянно разрабатывались.

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

Что такое Composition API и почему он был создан

Давайте рассмотрим, что делает фрагмент кода пригодным для повторного использования. Для меня есть три основных принципа повторного использования:

  • Code abstraction. Фрагмент кода является абстрактным, если он может соответствовать нескольким различным вариантам использования (например, классы во многих языках программирования).
  • Code portability. Кусок кода переносим, ​​когда его можно использовать не только в разных местах в одном проекте, но и в разных проектах.
  • Code decoupling (or loose coupling). Часть кода отделена от другой, когда изменение одного не требует изменения другого. Они максимально независимы друг от друга. Конечно, полное разделение невозможно, поэтому разработчики используют более точный термин «слабосвязанный».
Читайте также:  Цветная панель Matplotlib

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

Мотивация для добавления API Vue Composition в фреймворк

Мотивация для добавления Composition API в Vue 3 ясна и проста: создание более компактного и дефрагментированного кода. Давайте рассмотрим это немного подробнее.

Когда я впервые нашел Vue, меня зацепил его API опций (объектно-ориентированный). Мне он показался более понятным и элегантным по сравнению с эквивалентами Angular и React. У всего есть свое место, и я могу просто положить его туда. Когда у меня есть данные, я добавляю их в dataопцию; когда у меня есть какие-то функции, я добавляю их в methodsопцию и так далее:

// Options API example
export default {
  props: ['title', 'message'],

  data() {
    return {
      width: 30,
      height: 40
    }
  },

  computed: {
    rectArea() {
      return this.width * this.height
    },
  },

  methods: {
    displayMessage () {
      console.log(`${this.title}: ${this.message}`)
    }
  }
}

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

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

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

Вот упрощенный наглядный пример:

Вот упрощенный наглядный пример

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

Преимущества Vue Composition API

Вот краткое изложение основных преимуществ Composition API:

  • Лучшая композиция кода.
  • Логически связанные блоки хранятся вместе.
  • Лучшая общая производительность по сравнению с Vue 2.
  • Более чистый код. Код логически лучше упорядочен, что делает его более значимым и легким для чтения и понимания.
  • Простота извлечения и импорта функций.
  • Поддержка TypeScript, которая улучшает интеграцию IDE, поддержку кода и отладку кода. (Это не функция Composition API, но стоит упомянуть о ней как о функции Vue 3.)

Основы Composition API

Несмотря на свою мощность и гибкость, Composition API довольно прост. Чтобы использовать его в компоненте, нам нужно добавить setup()функцию, которая на самом деле является просто еще одной опцией, добавленной в API опций:

export default {
  setup() {
    // Composition API
  }
}

Внутри setup()функции мы можем создавать реактивные переменные и функции для управления ими. Затем мы можем вернуть те переменные и / или функции, которые должны быть доступны в остальной части компонента. Для того, чтобы реагирующие переменные, вы должны будете использовать Реакционная API функции ( ref(), reactive(), computed()и так далее). Чтобы узнать больше об их использовании, вы можете изучить это исчерпывающее руководство по системе Vue 3 Reactivity.

setup() Функция принимает два аргумента: propsи context.

Реквизиты являются реактивными и будут обновляться, когда будут переданы новые реквизиты:

export default {
  props: ["message"],
  setup(props) {
    console.log(props.message)
  }
}

Если вы хотите деструктурировать свой реквизит, вы можете сделать это, используя toRefs()внутри setup()функции. Если вместо этого вы используете деструктуризацию ES6, она удалит реактивность props :

import { toRefs } from 'vue'

export default {
  props: ["message"],
  setup(props) {
//  const { message } = props   <-- ES6 destructuring. The 'message' is NOT reactive now.
    const { message } = toRefs(props) // Using 'toRefs()' keeps reactivity.
    console.log(message.value)
  }
}

Контекст является нормальным объектом JavaScript (не реакционноспособный), который предоставляет другие полезные значения, как attrs, slots, emit. Эти эквиваленты $attrs, $slotsи $emitот API Options.

setup() Функция выполняется перед созданием компонента экземпляра. Таким образом, вы не будете иметь доступ к следующим опциям компонентов: data, computed, methods, и шаблон рефов.

В setup()функции вы можете получить доступ к ловушке жизненного цикла компонента с помощью onпрефикса. Например, mountedстанет onMounted. Функции жизненного цикла принимают обратный вызов, который будет выполняться при вызове ловушки компонентом:

export default {
  props: ["message"],
  setup(props) {
    onMounted(() => {
      console.log(`Message: ${props.message}`)
    })
  }
}

Примечание: вам не нужно явно вызывать перехватчики beforeCreateи created, потому что setup()функция сама выполняет аналогичную работу. В setup()функции thisне является ссылкой на текущий активный экземпляр, потому что setup()вызывается до разрешения других параметров компонента.

Сравнение API параметров с API композиции

Давайте быстро сравним API опций и составления.

 

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

<template>
  <div id="app">
    <h4> {{ name }}'s To Do List </h4>
    <div>
      <input v-model="newItemText" v-on:keyup.enter="addNewTodo" />
      <button v-on:click="addNewTodo">Add</button>
      <button v-on:click="removeTodo">Remove</button>
        <transition-group name="list" tag="ol">
          <li v-for="task in tasks" v-bind:key="task" >{{ task }}</li>
        </transition-group>
    </div>
  </div>
</template>
<script>
  export default {
    data() { 
      return {
        name: "Ivaylo",
        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.shift();
      },
    }
  }; 
</script> 

Я опустил здесь код CSS для краткости и потому, что он не имеет отношения к делу. Вы можете увидеть полный код в примере Vue 2 Options API.

Как видите, это довольно простой пример. У нас есть три переменных данных и два метода. Давайте посмотрим, как их переписать с учетом Composition API:

<script>
  import { ref, readonly } from "vue"

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

      const addNewTodo = () => {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value);
        }
        newItemText.value = "";
      }
      const removeTodo = () => {
        tasks.value.shift();
      }
      
      return {
        name: readonly(name),
        tasks: readonly(tasks),
        newItemText,
        addNewTodo,
        removeTodo
      }
    }
  }; 
</script> 

Как вы можете видеть в этом примере Vue 3 Composition API, функциональность такая же, но все переменные данных и методы перемещаются внутри setup()функции.

Чтобы воссоздать три реактивные переменные данных, мы используем ref()функцию. Затем мы воссоздаем файлы addNewTodo()и removeTodo()functions. Обратите внимание, что все варианты использования thisудалены, и вместо этого используются имена переменных, за которыми следует valueсвойство. Так что вместо того, this.newItemTextчтобы писать newItemText.value, и так далее. Наконец, мы возвращаем переменные и функции, чтобы их можно было использовать в шаблоне компонента. Обратите внимание: когда мы используем их в шаблоне, нам не нужно использовать valueсвойство, потому что все возвращаемые значения автоматически распаковываются. Так что нам не нужно ничего менять в шаблоне.

Мы делаем файлы nameи tasksтолько для чтения, чтобы предотвратить их изменение вне компонента. В этом случае tasksсвойство можно изменить только с помощью addNewTodo()и removeTodo().

Когда Composition API хорошо подходит для компонента, а когда нет

То, что создается какая-то новая технология, не означает, что она вам нужна или вы должны ее использовать. Прежде чем решить, стоит ли использовать новую технологию, следует подумать, действительно ли она вам нужна. Хотя Composition API предлагает некоторые большие преимущества, его использование в небольших и простых проектах может привести к ненужной сложности. Принцип такой же, как и с использованием Vuex : он может быть слишком сложным для небольших проектов.

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

Так что вы можете взять следующие правила в качестве общего совета:

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

Что такое Vue Composables?

Секрет оружие Состав API является возможность создания высоко многоразовые модули, называемые composables. Они позволяют нам извлекать реактивное состояние и функциональность и повторно использовать их в других компонентах. Компоненты — это эквивалент миксинов в API опций. Их также можно рассматривать как эквивалент перехватчиков React.

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

Служебные функции

Служебные функции полезны, но ограничены, потому что они не могут обрабатывать специфичные для Vue функции, такие как реактивное состояние. Вот пример:

// utils.js 
export function increment(count) {
  return count++;
}
...

Здесь у нас есть increment(count)функция полезности, которая увеличивает значение счетчика на единицу. Но мы не можем здесь определить реактивное состояние. Нам нужно добавить реактивную countпеременную внутри потребляющего компонента, например:

// Counter.vue
<template>
  <p>{{ count }}</p>
  <button v-on:click="increment(count)">Increment</button>
</template>

import { increment } from './utils.js'

export default {
  data() {
    return { count: 0 }
  }
}

Компоненты без рендеринга

Компоненты без рендеринга (которые представляют собой компоненты, которые не отображают никаких HTML-шаблонов, а только состояние и функциональность) немного лучше, чем служебные функции, поскольку они могут обрабатывать специфичные для Vue функции, но их гибкость также ограничена. Вот пример:

// RenderlessCounter.vue
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  render() {
    return this.$slots.default({
      count: this.count,
      increment: this.increment
  });
}

Здесь немного лучше, потому что мы можем определить реактивное состояние и экспортировать его с помощью слотов с ограниченной областью видимости. Когда мы реализуем компонент, мы используем определенную countпеременную и increment()метод для создания настраиваемого шаблона:

// Counter.vue
<renderless-counter>
  <template v-slot:default="{count, increment}">
    <p>{{ count }}</p>
    <button v-on:click="increment">Increment</button>
  </template>
</renderless-counter>

Mixins

Миксины — это официальный способ разделения кода между компонентами, созданными с помощью Options API. Примесь — это просто экспортированный объект опций:

// CounterMixin.js
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

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

// Counter.vue
<template>
  <p>{{ count }}</p>
  <button v-on:click="increment">Increment</button>
</template>

import CounterMixin from './CounterMixin'

export default {
  mixins: [CounterMixin]
}

Если компонент уже определил некоторые параметры ( data, methods, computedи так далее), они объединяются с теми, от импортируемого Mixin (ов). Как мы вскоре увидим, у такого поведения есть ряд серьезных недостатков.

У миксинов есть ряд серьезных недостатков по сравнению с композитами:

  • Источник данных скрыт. Когда данные компонента поступают из нескольких миксинов, мы не можем точно сказать, какие свойства были получены из какого миксина. То же самое верно и при использовании глобально зарегистрированных миксинов.
  • Ограниченное повторное использование. Миксины не принимают параметры, поэтому мы не можем добавить дополнительную логику.
  • Конфликт имен. Если у двух или более миксинов есть свойства с одинаковыми именами, будет использоваться свойство из последнего миксина, что может быть не тем, что нам нужно.
  • Нет защиты данных. Мы не можем быть уверены, что свойство миксина не будет изменено компонентом-потребителем.

Преимущества Vue Composables

В заключение этого раздела давайте подытожим основные преимущества составных модулей Vue 3:

  • Источник данных прозрачен. Чтобы использовать составные элементы, нам нужно импортировать их и использовать деструктурирование для извлечения желаемых данных. Таким образом, мы можем ясно видеть источник каждого свойства / метода.
  • Никаких конфликтов имен. Мы можем использовать свойства с одинаковыми именами из нескольких составных элементов, просто переименовав их.
  • Данные защищены. Мы можем сделать возвращаемые свойства доступными только для чтения, тем самым ограничив мутации, исходящие от других компонентов. Принцип такой же, как и с мутациями во Vuex.
  • Общее состояние. Обычно каждый составной объект, используемый в компоненте, создает новое локальное состояние. Но мы также можем определить глобальное состояние, чтобы при использовании составных элементов в разных компонентах они имели одно и то же состояние.

Создание и использование Vue Composables

В этом разделе мы узнаем, как создавать и использовать настраиваемые составные элементы Vue 3.

Примечание: для этого проекта вам потребуются установленные на вашем компьютере Node и Vue CLI.

Давайте создадим новый проект Vue 3 с помощью Vue CLI :

vue create vue-composition-api-examples

Когда вас попросят выбрать пресет, убедитесь, что вы выбрали вариант Vue 3 по умолчанию.

Вы можете найти все файлы проекта в репозитории примеров Vue Composition API.

Создание составного объекта для извлечения данных

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

Сначала создайте src/composablesпапку и добавьте в нее useFetch.jsфайл. Вот код этого файла:

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

export function useFetch(url, options) {
  const data = ref(null);
  const state = reactive({
    error: null,
    loading: false
  });

  const fetchData = async () => {
    state.loading = true;
    try {
      const res = await fetch(url, options);
      data.value = await res.json();
    } catch (e) {
      state.error = e;
    } finally {
      state.loading = false;
    }
  };

  fetchData();
  
  return {data, ...toRefs(state)};
}

Технически составной объект — это просто функция, которую мы экспортируем ( useFetch()в нашем случае). В этой функции мы создаем dataи stateпеременные. Затем мы создаем fetchData()функцию, в которой используем Fetch API для получения данных из определенного источника и присвоения результата dataсвойству. После fetchData()функции мы сразу ее вызываем, чтобы присвоить переменным выбранные данные. Наконец, мы возвращаем все переменные. Мы используем toRefs()здесь, чтобы правильно извлекать errorи loadingпеременные, сохраняя их реактивность.

Большой! Теперь давайте посмотрим, как мы можем использовать наш составной компонент в компоненте.

В src/componentsпапке добавьте UserList.vueфайл следующего содержания:

<template>
  <div v-if="error">
    <h2>Error: {{ error }}</h2>
  </div>
  <div v-if="loading">
    <h2>Loading data...</h2>
  </div>
  <h2>Users</h2>
  <ul v-for="item in data" :key="item.id">
    <li><b>Name:</b> {{ item.name }} </li>
    <li><b>Username:</b> {{ item.username}} </li>
  </ul>
</template>

<script>
import { useFetch } from '../composables/useFetch.js';

export default {
  setup() {
    const {data, error, loading} = useFetch(
      'https://jsonplaceholder.typicode.com/users',
      {}
    );
   
    return {
      data,
      error,
      loading
    };
  }
};
</script> 

<style scoped>
  ul {
    list-style-type: none;
  }
</style>

Здесь мы импортируем useFetch()составной объект, а затем извлекаем его переменные внутри setup()функции. После того, как мы вернули переменные, мы можем использовать их в шаблоне для создания списка пользователей. В шаблоне мы используем v-ifдирективу для проверки истинности errorи loading, и если одна из них истинна, отображается соответствующее сообщение. Затем мы используем v-forдирективу и dataсвойство для создания фактического списка пользователей.

Последнее, что нам нужно сделать, это добавить компонент в App.vueфайл. Откройте App.vueфайл и замените его содержимое следующим:

<template>
  <div id="app">
    <user-list />
  </div>
</template>

<script>
import UserList from "./components/UserList";

export default {
  name: "App",
  components: {
    UserList
  }
};
</script>

Вот и все. Это основа для создания и использования составных компонентов. Но давайте пойдем дальше и сделаем компонент списка пользователей более гибким и многоразовым.

Создание многоразового компонента

Переименовать, UserList.vueчтобы UniversalList.vueи замените его содержимое следующим:

<template>
  <div v-if="error">
    <h2>Error: {{ error }}</h2>
  </div>
  <div v-if="loading">
    <h2>Loading data...</h2>
  </div>
  <slot :data="data"></slot>
</template>

<script>
import { useFetch } from '../composables/useFetch.js';

export default {
  props: ['url'],
  setup(props) {
    const {data, error, loading} = useFetch(
      props.url,
      {}
    );
   
    return {
      data,
      error,
      loading
    };
  }
};
</script> 

Здесь есть два важных изменения. Во-первых, когда мы вызываем useFetch(), вместо того, чтобы явно добавлять URL, мы заменяем его urlопорой. Таким образом, мы могли бы использовать другой URL-адрес в зависимости от наших потребностей. Во-вторых, вместо готового шаблона для списка мы добавляем компонент слота и предоставляем его в dataкачестве его опоры. Таким образом, мы могли бы использовать любой шаблон, который нам нужен при реализации компонента. Посмотрим, как это сделать на практике.

Замените содержимое App.vueна следующее:

<template>
  <div id="app">
    <universal-list url="https://jsonplaceholder.typicode.com/todos" v-slot="{ data }">
      <h2>Todos</h2>
      <ol>
        <li v-for="item in data" :key="item.id"> {{ item.title }} - {{ item.completed }} </li>
      </ol>
    </universal-list>
  </div>
</template>

<script>
import UniversalList from "./components/UniversalList";

export default {
  name: "App",
  components: {
    UniversalList
  }
};
</script>

Теперь, когда мы включаем компонент универсального списка, мы можем предоставить настраиваемый шаблон в зависимости от наших потребностей. Мы добавляем желаемый URL и используем v-slotдирективу для получения данных из useFetch()составного объекта. Наконец, мы структурируем полученные данные по своему желанию. В нашем случае это список задач.

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

Заключение

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

Оцените статью
bestprogrammer.ru
Добавить комментарий