Что нового в Vue 3?

Опубликовано: 2022-03-10
Краткое резюме ↬ Vue 3 поставляется с множеством интересных новых функций и изменений в некоторых из существующих, которые направлены на то, чтобы сделать разработку с использованием фреймворка намного проще и удобнее в сопровождении. В этой статье мы рассмотрим некоторые из этих новых функций и то, как начать работу с ними. Мы также рассмотрим некоторые изменения, внесенные в существующие функции.

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

В конце этого руководства читатели узнают;

  1. Знайте о provide / inject и как его использовать.
  2. Иметь базовое представление о Телепорте и о том, как его использовать.
  3. Узнайте о фрагментах и ​​о том, как их использовать.
  4. Знайте об изменениях, внесенных в Global Vue API.
  5. Узнайте об изменениях, внесенных в Events API.

Эта статья предназначена для тех, кто хорошо понимает Vue 2.x. Вы можете найти весь код, используемый в этом примере, на GitHub.

provide / inject

В Vue 2.x у нас были props , которые упрощали передачу данных (строки, массивы, объекты и т. д.) из родительского компонента непосредственно в его дочерний компонент. Но во время разработки мы часто находили случаи, когда нам нужно было передать данные из родительского компонента в глубоко вложенный компонент, что было сложнее сделать с помощью props . Это привело к использованию Vuex Store, Event Hub, а иногда и к передаче данных через глубоко вложенные компоненты. Давайте посмотрим на простое приложение;

Важно отметить, что Vue 2.2.0 также поставлялся с provide / inject , которое не рекомендовалось использовать в универсальном коде приложения.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
 # childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

Здесь у нас есть целевая страница с раскрывающимся списком цветов, и мы передаем выбранный color в childComponent.vue в качестве реквизита. Этот дочерний компонент также имеет msg , которое принимает текст для отображения в разделе шаблона. Наконец, у этого компонента есть дочерний компонент ( colorComponent.vue ), который принимает свойство color от родительского компонента, которое используется при определении класса для текста в этом компоненте. Это пример передачи данных через все компоненты.

Но с Vue 3 мы можем сделать это чище и короче, используя новую пару Provide и inject. Как следует из названия, мы используем provide как функцию, либо как объект, чтобы сделать данные доступными из родительского компонента для любого из его вложенных компонентов, независимо от того, насколько глубоко вложен такой компонент. Мы используем форму объекта при передаче жестко закодированных значений, чтобы provide подобное;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>

Но для случаев, когда вам нужно передать свойство экземпляра компонента для provide , мы используем режим функции, поэтому это возможно;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>

Поскольку нам не нужны свойства color как в childComponent.vue , так и в colorComponent.vue , мы избавляемся от них. Преимущество использования provide заключается в том, что родительскому компоненту не нужно знать, какому компоненту нужно свойство, которое он предоставляет.

Чтобы использовать это в компоненте, который в данном случае нуждается в этом, colorComponent.vue мы делаем это;

 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

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

Мы могли бы заметить, что если мы попытаемся выбрать новый цвет с помощью раскрывающегося списка, цвет не обновится в colorComponent.vue , потому что по умолчанию свойства в условии не provide реактивными. Чтобы исправить это, мы используем computed метод.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>

Здесь мы импортируем computed и передаем наш selectedColor , чтобы он мог реагировать и обновляться, когда пользователь выбирает другой цвет. Когда вы передаете переменную вычисляемому методу, он возвращает объект со value . Это свойство содержит значение вашей переменной, поэтому для этого примера нам нужно обновить colorComponent.vue , чтобы он выглядел следующим образом;

 # colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

Здесь мы меняем color на color.value , чтобы представить изменение после того, как color стал реактивным с использованием computed метода. В этот момент class текста в этом компоненте всегда будет изменяться при каждом изменении selectedColor в родительском компоненте.

Еще после прыжка! Продолжить чтение ниже ↓

Телепорт

Бывают случаи, когда мы создаем компоненты и размещаем их в одной части нашего приложения из-за логики, которую использует приложение, но предназначены для отображения в другой части нашего приложения. Типичным примером этого может быть модальное или всплывающее окно, которое предназначено для отображения и покрытия всего экрана. Хотя мы можем создать обходной путь для этого, используя свойство CSS position для таких элементов, с Vue 3 мы также можем использовать Teleport.

Телепорт позволяет нам извлечь компонент из его исходного положения в документе, из контейнера #app по умолчанию, в который заключены приложения Vue, и переместить его в любой существующий элемент на используемой странице. Хорошим примером может быть использование Teleport для перемещения компонента заголовка из div #app в header . Важно отметить, что вы можете телепортироваться только к элементам, которые существуют за пределами Vue DOM.

сообщение об ошибке в консоли при телепортации к недопустимому элементу
Сообщение об ошибке телепортации в консоли: Недопустимое сообщение об ошибке цели телепортации в терминале. (Большой превью)

Компонент Teleport принимает два реквизита, которые определяют поведение этого компонента, и они есть;

  1. to
    Это свойство принимает имя класса, идентификатор, элемент или атрибут data-*. Мы также можем сделать это значение динамическим, передав :to prop вместо to и динамически изменив элемент Teleport.
  2. :disabled
    Это свойство принимает Boolean и может использоваться для переключения функции телепортации на элементе или компоненте. Это может быть полезно для динамического изменения положения элемента.

Идеальный пример использования Телепорта выглядит так;

 # index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>

В файле index.html по умолчанию в вашем приложении Vue мы добавляем элемент header , потому что мы хотим телепортировать наш компонент заголовка в эту точку в нашем приложении. Мы также добавили класс к этому элементу для стилизации и для удобства ссылок в нашем компоненте Teleport.

 # Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>

Здесь мы создаем компонент заголовка и добавляем логотип со ссылкой на домашнюю страницу нашего приложения. Мы также добавляем компонент Teleport и присваиваем to свойства header значение, потому что мы хотим, чтобы этот компонент отображался внутри этого элемента. Наконец, мы импортируем этот компонент в наше приложение;

 # App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>

В этом файле мы импортируем компонент заголовка и помещаем его в шаблон, чтобы он был виден в нашем приложении.

Теперь, если мы проверим элемент нашего приложения, мы заметим, что наш компонент заголовка находится внутри элемента header ;

Компонент заголовка в DevTools
Компонент заголовка в DevTools (большой предварительный просмотр)

Фрагменты

В Vue 2.x было невозможно иметь несколько корневых элементов в template вашего файла, и в качестве обходного пути разработчики начали заключать все элементы в родительский элемент. Хотя это не выглядит серьезной проблемой, бывают случаи, когда разработчики хотят визуализировать компонент без контейнера, обертывающего такие элементы, но вынуждены с этим смириться.

В Vue 3 была представлена ​​новая функция под названием «Фрагменты», которая позволяет разработчикам иметь несколько элементов в корневом файле шаблона. Итак, в Vue 2.x вот как будет выглядеть компонент-контейнер поля ввода;

 # inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>

Здесь у нас есть простой компонент элемента формы, который принимает два реквизита, label и type , а раздел шаблона этого компонента заключен в div. Это не обязательно проблема, но если вы хотите, чтобы метка и поле ввода находились непосредственно внутри элемента form . С Vue 3 разработчики могут легко переписать этот компонент, чтобы он выглядел следующим образом.

 # inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>

С одним корневым узлом атрибуты всегда приписываются корневому узлу, и они также известны как несобственные атрибуты . Это события или атрибуты, передаваемые компоненту, у которых нет соответствующих свойств, определенных в props или emits . Примерами таких атрибутов являются class и id . Однако необходимо явно определить, какой из элементов в многокорневом компоненте узла должен быть атрибутирован.

Вот что это означает, используя inputComponent.vue сверху;

  1. При добавлении class к этому компоненту в родительском компоненте необходимо указать, к какому компоненту будет отнесен этот class , иначе атрибут не действует.
 <template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>

Когда вы делаете что-то подобное, не определяя, куда должны быть атрибутированы атрибуты, вы получаете это предупреждение в своей консоли;

сообщение об ошибке в терминале, когда атрибуты не распределены
Сообщение об ошибке в терминале, когда атрибуты не распределены (большой предварительный просмотр)

И border не влияет на компонент;

компонент без распределения атрибутов
Компонент без распределения атрибутов (большой предварительный просмотр)
  1. Чтобы исправить это, добавьте v-bind="$attrs" к элементу, которому вы хотите передать такие атрибуты;
 <template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>

Здесь мы говорим Vue, что хотим, чтобы атрибуты были распределены по элементу label , что означает, что мы хотим применить к нему awesome__class . Теперь, если мы проверим наш элемент в браузере, мы увидим, что класс теперь добавлен к label , и, следовательно, вокруг метки теперь есть рамка.

компонент с распределением атрибутов
Компонент с распределением атрибутов (большой предварительный просмотр)

Глобальный API

Нередко можно было увидеть Vue.component или Vue.use в файле main.js приложения Vue. Эти типы методов известны как глобальные API, и в Vue 2.x их довольно много. Одна из проблем этого метода заключается в том, что он делает невозможным изолировать определенные функции для одного экземпляра вашего приложения (если у вас есть более одного экземпляра в вашем приложении), не затрагивая другие приложения, потому что все они смонтированы на Vue. Это то, что я имею в виду;

 Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })

Для приведенного выше кода невозможно указать, что директива Vue связана с app1 а Mixin — с app2 , но вместо этого они оба доступны в двух приложениях.

Vue 3 поставляется с новым глобальным API в попытке решить эту проблему с введением createApp . Этот метод возвращает новый экземпляр приложения Vue. Экземпляр приложения предоставляет подмножество текущих глобальных API. При этом все API-интерфейсы (компонент, примесь, директива, использование и т. д.), которые изменяют Vue из Vue 2.x, теперь будут перемещены в отдельные экземпляры приложения, и теперь каждый экземпляр вашего приложения Vue может иметь функции, уникальные для их, не затрагивая другие существующие приложения.

Теперь приведенный выше код можно переписать как;

 const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })

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

API событий

Один из наиболее распространенных способов, принятых разработчиками для передачи данных между компонентами, у которых нет отношения родитель-потомок, кроме использования Vuex Store, — это использование шины событий. Одна из причин, по которой этот метод распространен, заключается в том, что с ним легко начать работу;

 # eventBus.js const eventBus = new Vue() export default eventBus;

После этого следующим шагом будет импорт этого файла в main.js , чтобы сделать его глобально доступным в нашем приложении, или импортировать его в файлы, которые вам нужны;

 # main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus

Теперь вы можете генерировать события и прослушивать генерируемые события, подобные этому;

 this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')

Существует много кодовой базы Vue, заполненной подобным кодом. Однако с Vue 3 это было бы невозможно сделать, потому что $on , $off и $once были удалены, но $emit все еще доступен, потому что дочерний компонент должен передавать события своим родительским компонентам. Альтернативой этому может быть использование provide / inject или любой из рекомендованных сторонних библиотек.

Заключение

В этой статье мы рассмотрели, как вы можете передавать данные от родительского компонента к глубоко вложенному дочернему компоненту, используя пару « provide / inject ». Мы также рассмотрели, как мы можем перемещать и перемещать компоненты из одной точки нашего приложения в другую. Еще одна вещь, которую мы рассмотрели, — это компонент узла с несколькими корнями и то, как мы распределяем атрибуты, чтобы они работали правильно. Наконец, мы также рассмотрели изменения в Events API и Global API.

Дополнительные ресурсы

  • «Функции фабрики JavaScript с ES6+», Эрик Эллиотт, Medium
  • «Использование шины событий для совместного использования реквизитов между компонентами Vue», Кингсли Сайлас, CSS-Tricks
  • Использование нескольких телепортов на одну и ту же цель, документы Vue.js
  • Несобственные атрибуты, документация по Vue.js
  • Работа с реактивностью, документы Vue.js
  • teleport , документы Vue.js
  • Фрагменты, документы Vue.js
  • 2.x Синтаксис, документы Vue.js