Что нового в Vue 3?
Опубликовано: 2022-03-10С выпуском Vue 3 разработчикам приходится переходить с Vue 2, так как он предлагает несколько новых функций, которые очень полезны для создания легко читаемых и поддерживаемых компонентов, а также улучшенных способов структурирования нашего приложения в Vue. В этой статье мы рассмотрим некоторые из этих функций.
В конце этого руководства читатели узнают;
- Знайте о
provide / inject
и как его использовать. - Иметь базовое представление о Телепорте и о том, как его использовать.
- Узнайте о фрагментах и о том, как их использовать.
- Знайте об изменениях, внесенных в Global Vue API.
- Узнайте об изменениях, внесенных в 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 принимает два реквизита, которые определяют поведение этого компонента, и они есть;
-
to
Это свойство принимает имя класса, идентификатор, элемент или атрибут data-*. Мы также можем сделать это значение динамическим, передав:to
prop вместоto
и динамически изменив элемент Teleport. -
: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
;

Фрагменты
В 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
сверху;
- При добавлении
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
не влияет на компонент;

- Чтобы исправить это, добавьте
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