Динамическая регистрация компонентов во Vue
Если вы используете однофайловые компоненты Vue, то вам, конечно же, знаком способ подключения компонента внутри другого компонента:
- импортируем компонент;
- регистрируем его в объекте компонентов родителя;
- используем компонент в шаблоне
<template>
<some-random-thing />
</template>
<script>
import SomeRandomThing from './components/SomeRandomThing'
export default {
components: {
SomeRandomThing,
},
}
</script>
Это привычный паттерн. Но что если нам понадобится динамическое отображение компонента? Давайте посмотрим как нам разнообразить отношения с компонентами.
Итак, у нас есть компонент Header. Допустим, информация в хедере может меняться в зависимости от каких-то условий. Далее, у нас есть компоненты UserInfo и CompanyInfo. И мы хотим показывать какой-то из них в зависимости от какого-то условия. Как это сделать?
Способ 1: старый добрый и проверенный
Этот способ мы обсуждали в самом начале. Именно его выберет большинство разработчиков.
<template>
<div>
<company-info v-if="isCompany" />
<user-info v-else />
...
</div>
</template>
<script>
import UserInfo from './components/UserInfo'
import CompanyInfo from './components/CompanyInfo'
export default {
components: {
UserInfo,
CompanyInfo
},
props: {
isCompany: { type: Boolean, default: false },
},
}
</script>
Ничего необычного. Мы импортируем два компонента, регистрируем их, и показываем один из них в зависимости от значения входного параметра.
Вы наверняка постоянно так делаете. Но то же самое можно сделать немного лучше.
<component />
Способ 2: используем Component это встроенное во Vue определение компонента. Он работает как плейсхолдер для отображения любого компонента, имя которого передаётся через параметр :is
.
<template>
<div>
<component :is="componentName" />
</div>
</template>
<script>
import UserInfo from './components/UserInfo'
import CompanyInfo from './components/CompanyInfo'
export default {
components: {
UserInfo,
CompanyInfo,
},
props: {
isCompany: { type: Boolean, default: false },
},
computed: {
componentName () {
return this.isCompany ? 'company-info' : 'user-info'
},
},
}
</script>
Здесь мы используем <component />
и создали вычисляемое свойство с именем необходимого компонента, что позволило отказаться от громоздкой конструкции v-if/v-else
.
Но это ещё не всё. Несмотря на использование <component />
, нам по прежнему приходится импортировать и регистрировать компоненты UserInfo и CompanyInfo. Вот если бы можно было импортировать только необходимый компонент... И тут нам поможет динамический импорт
<component />
(и бонусом разделение кода)
Способ 3: динамический импорт + <template>
<div>
<component :is="componentInstance" />
</div>
</template>
<script>
export default {
props: {
isCompany: { type: Boolean, default: false },
},
computed: {
componentInstance () {
const name = this.isCompany ? 'CompanyInfo' : 'UserInfo'
return () => import(`./components/${name}`)
}
}
}
</script>
В этом варианте для импорта компонента мы используем функцию, которая возвращает Promise. И если всё идёт хорошо, то Promise разрешается и загружается только необходимый нам компонент, который мы передаём <component />
для рендеринга. Да, так можно. Согласно документации, свойство :is
может содержать:
- Имя компонента или
- Объект компонента
А объект компонента это именно то, что нам нужно.
И вот мы существенно сократили наш код, нам больше не нужно вручную импортировать и регистрировать компоненты. Всё происходит динамически. Более подробную информацию о динамическом импорте смотрите в документации.
Таким образом мы немного упростили себе жизнь, сократив количество кода, и мимоходом уменьшили объём итогового файла приложения. Ведь динамический импорт он на то и динамический, что необходимый компонент будет подгружаться только при необходимости.