# Изменения в глобальном API
кардинальное изменение
Vue 2.x имеет ряд глобальных API и конфигураций, которые глобально изменяют поведение Vue. Например, для регистрации глобального компонента можно воспользоваться методом API Vue.component
:
Vue.component('button-counter', {
data: () => ({
count: 0
}),
template: '<button @click="count++">Счётчик нажатий — {{ count }}.</button>'
})
2
3
4
5
6
Аналогичным образом объявляется глобальная директива:
Vue.directive('focus', {
inserted: el => el.focus()
})
2
3
Несмотря на удобство такого подхода, он приводит к нескольким проблемам. Технически, Vue 2 не имеет концепта «приложения». То что считаем приложением — просто корневой экземпляр Vue, созданный с помощью new Vue()
. Каждый корневой экземпляр, созданный из одного и того же конструктора Vue будет иметь одну и ту же глобальную конфигурацию. В результате:
Глобальная конфигурация позволяет легко случайно загрязнить другие тестовые случаи во время тестирования. Пользователям потребуется аккуратно хранить оригинальную глобальную конфигурацию и восстанавливать её после каждого теста (например, сбрасывать
Vue.config.errorHandler
). Некоторые API, такие какVue.use
иVue.mixin
даже не предоставляют возможности откатить свои изменения и эффекты. Это приводит к тому, что тесты с участием плагинов становятся очень хитрыми. Фактически, vue-test-utils должны реализовывать специальный APIcreateLocalVue
чтобы справиться с этим:import { createLocalVue, mount } from '@vue/test-utils' // создание расширенного конструктора `Vue` const localVue = createLocalVue() // установка плагина «глобально» в «локальном» конструкторе Vue localVue.use(MyPlugin) // передача `localVue` в опции монтирования mount(Component, { localVue })
1
2
3
4
5
6
7
8
9
10Глобальная конфигурация затрудняет совместное использование одной и той же копии Vue между несколькими «приложениями» на одной странице с различными глобальными конфигурациями.
// это повлияет на оба корневых экземпляра Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })
1
2
3
4
5
6
7
Чтобы избежать этих проблем, во Vue 3 представляем…
# Новый глобальный API: createApp
Вызов createApp
возвращает экземпляр приложения, новый концепт во Vue 3.
import { createApp } from 'vue'
const app = createApp({})
2
3
При использовании сборки с CDN createApp
доступен через глобальный объект Vue
:
const { createApp } = Vue
const app = createApp({})
2
3
Экземпляр приложения представляет собой подмножество от глобального API Vue 2. Главное правило заключается в том, что любое API, которое глобально изменяет поведение Vue теперь переносится в экземпляр приложения. Ниже таблица соответствий глобального API Vue 2 и соответствующих API экземпляра:
Глобальное API в 2.x | API экземпляра (app ) в 3.x |
---|---|
Vue.config | app.config |
Vue.config.productionTip | удалено (см. ниже) |
Vue.config.ignoredElements | app.config.compilerOptions.isCustomElement (см. ниже) |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use (см. ниже) |
Vue.prototype | app.config.globalProperties (см. ниже) |
Vue.extend | удалён (см. ниже) |
Все другие глобальные API, которые глобально не изменяют поведение, теперь доступны именованными экспортами, как описывается в разделе Treeshaking глобального API.
# Удалено свойство config.productionTip
Во Vue 3.x, подсказка «use production build» отображается только при использовании «dev + full build» (сборка, с компилятором шаблонов и отображением предупреждений).
Так как сборки в виде ES-модулей используются с системами сборки, и в большинстве случаев CLI или шаблон конфигурируют переменную окружения сборки, то эти предупреждения теперь больше не будут показываться.
Флаг сборки для миграции: CONFIG_PRODUCTION_TIP
# Свойство config.ignoredElements
теперь config.compilerOptions.isCustomElement
Эта опция конфигурации добавлена для поддержки нативных пользовательских элементов, поэтому переименование лучше отражает что она делает. Новая опция ожидает функцию, что обеспечивает большую гибкость, нежели старый подход со строками и RegExp:
// раньше
Vue.config.ignoredElements = ['my-el', /^ion-/]
// теперь
const app = createApp({})
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('ion-')
2
3
4
5
6
7
Внимание
В 3.х, проверка, является ли элемент компонентом или нет, была перенесена на этап компиляции шаблона, поэтому данная опция конфигурации используется только при компиляции шаблонов на лету. При использовании только-runtime сборки, опция isCustomElement
должна передаваться в @vue/compiler-dom
в настройках сборки — например, через опцию compilerOptions
в vue-loader (opens new window).
- Если
config.compilerOptions.isCustomElement
указан при использовании только-runtime сборки, то будет выведено предупреждение о необходимости передавать опцию в настройках сборки; - Это будет новая опция верхнего уровня в конфигурации Vue CLI.
Флаг сборки для миграции: CONFIG_IGNORED_ELEMENTS
# Свойство Vue.prototype
заменено на config.globalProperties
Во Vue 2, расширение Vue.prototype
обычно использовалось для добавления свойств, которые были бы доступны во всех компонентах.
Эквивалентом во Vue 3 теперь будет config.globalProperties
. Эти свойства будут скопированы как часть процесса инициализации компонента внутри приложения:
// раньше - Vue 2
Vue.prototype.$http = () => {}
2
// теперь - Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
2
3
4
Использование provide
(обсуждается ниже) также следует рассматривать как альтернативу globalProperties
.
Флаги сборки для миграции: GLOBAL_PROTOTYPE
# Удалён метод Vue.extend
Во Vue 2.x метод Vue.extend
использовался для создания «подкласса» базового конструктора Vue с аргументом, который должен быть объектом, содержащим опции компонента. Во Vue 3.x больше нет концепции конструкторов компонентов. Монтируемый компонент всегда должен использовать глобальный API createApp
:
// раньше - Vue 2
// создание конструктора
const Profile = Vue.extend({
template: '<p>{{ firstName }} {{ lastName }} — {{ alias }}</p>',
data() {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// создание экземпляра Profile и его монтирование на элементе
new Profile().$mount('#mount-point')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// сейчас - Vue 3
const Profile = {
template: '<p>{{ firstName }} {{ lastName }} — {{ alias }}</p>',
data() {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
}
Vue.createApp(Profile).mount('#mount-point')
2
3
4
5
6
7
8
9
10
11
12
13
# Выведение типов
Во Vue 2 метод Vue.extend
также использовался с TypeScript для выведения типов опций компонента. Во Vue 3 можно использовать глобальный API defineComponent
вместо Vue.extend
для этих же целей.
Обратите внимание, что хотя возвращаемый тип defineComponent
является типом, подобным конструктору, он используется только для вывода TSX. Во время выполнения defineComponent
по большей части ничего не делает и возвращает объект опций как есть.
# Наследование компонентов
Во Vue 3 рекомендуется отдавать предпочтение композиции через Composition API вместо наследования или примесей. Если по какой-то причине всё ещё требуется наследование компонентов, то можно воспользоваться опцией extends
вместо Vue.extend
.
Флаги сборки для миграции: GLOBAL_EXTEND
# Примечание для разработчиков плагинов
Частая практика разработчиков плагинов — устанавливать плагины автоматически в UMD-сборках с помощью Vue.use
. Например, официальный плагин vue-router
устанавливается в окружении браузера таким образом:
var inBrowser = typeof window !== 'undefined'
/* … */
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
2
3
4
5
Так как глобальный API use
больше недоступен во Vue 3, этот метод перестанет работать, а вызов Vue.use()
теперь выведет предупреждение. Вместо этого, пользователь теперь должен явно указать использования плагина в экземпляре приложения:
const app = createApp(MyApp)
app.use(VueRouter)
2
# Монтирование экземпляра приложения
После инициализации через createApp(/* опции */)
, экземпляр приложения app
можно использовать для монтирования корневого экземпляра компонента через app.mount(domTarget)
:
import { createApp } from 'vue'
import MyApp from './MyApp.vue'
const app = createApp(MyApp)
app.mount('#app')
2
3
4
5
Со всеми этими изменениями, компонент и директива, о которых обсуждали в начале этого руководства, будут переписаны примерно таким образом:
const app = createApp(MyApp)
app.component('button-counter', {
data: () => ({
count: 0
}),
template: '<button @click="count++">Счётчик нажатий — {{ count }}.</button>'
})
app.directive('focus', {
mounted: el => el.focus()
})
// теперь каждый экземпляр приложения, смонтированный с помощью app.mount(),
// вместе со своим деревом компонентов, будет иметь тот же самый компонент
// “button-counter” и директиву “focus”, не загрязняя глобальное окружение
app.mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Флаги сборки для миграции: GLOBAL_MOUNT
# Provide / Inject
Аналогично опции provide
в корневом экземпляре в 2.x, экземпляр приложения во Vue 3 также может предоставлять зависимости, которые могут внедряться любым компонентом внутри приложения:
// в точке создания экземпляра приложения
app.provide('guide', 'Руководство по Vue 3')
// в дочернем компоненте
export default {
inject: {
book: {
from: 'guide'
}
},
template: `<div>{{ book }}</div>`
}
2
3
4
5
6
7
8
9
10
11
12
Использование provide
особенно полезно при создании плагина, в качестве альтернативы свойству globalProperties
.
# Общая конфигурация между приложениями
Одним из способов создания общей конфигурации, например из компонентов или директив, для использования между приложениями — создать функцию фабрику, подобную такой:
import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
const createMyApp = options => {
const app = createApp(options)
app.directive('focus', /* ... */)
return app
}
createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
2
3
4
5
6
7
8
9
10
11
12
13
Теперь директива focus
будет доступна как в экземплярах Foo
и Bar
, так и в их потомках.