FRONTEND BLOG

Динамическая регистрация компонентов во 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>

Ничего необычного. Мы импортируем два компонента, регистрируем их, и показываем один из них в зависимости от значения входного параметра.

Вы наверняка постоянно так делаете. Но то же самое можно сделать немного лучше.

Способ 2: используем <component />

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. Вот если бы можно было импортировать только необходимый компонент... И тут нам поможет динамический импорт

Способ 3: динамический импорт + <component /> (и бонусом разделение кода)

<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 может содержать:

  • Имя компонента или
  • Объект компонента

А объект компонента это именно то, что нам нужно.

И вот мы существенно сократили наш код, нам больше не нужно вручную импортировать и регистрировать компоненты. Всё происходит динамически. Более подробную информацию о динамическом импорте смотрите в документации.

Таким образом мы немного упростили себе жизнь, сократив количество кода, и мимоходом уменьшили объём итогового файла приложения. Ведь динамический импорт он на то и динамический, что необходимый компонент будет подгружаться только при необходимости.