← Курс/Динамические компоненты: <component :is>#226 из 257+25 XP

Динамические компоненты: <component :is>

Базовый синтаксис

Специальный тег <component> с привязкой :is позволяет динамически выбирать, какой компонент рендерить:

<template>
  <component :is="currentComponent" />
</template>
import TabA from './TabA.vue'
import TabB from './TabB.vue'
import TabC from './TabC.vue'

const tabs = { TabA, TabB, TabC }
const currentTab = ref('TabA')

const currentComponent = computed(() => tabs[currentTab.value])

Передача props динамическому компоненту

<component :is> поддерживает все обычные атрибуты и директивы:

<component
  :is="currentComponent"
  :user="user"
  :config="config"
  @action="handleAction"
/>

:is со строкой vs объектом компонента

Атрибут :is принимает два вида значений:

// 1. Строка — имя зарегистрированного компонента
const currentTab = ref('MyComponent')  // должен быть зарегистрирован глобально

// 2. Объект компонента — импортированный напрямую (рекомендуется)
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const current = ref(HomeView)

resolveComponent()

Когда имя компонента известно только как строка, используйте resolveComponent():

import { resolveComponent, h } from 'vue'

// Внутри setup() или render функции
const MyComp = resolveComponent('MyRegisteredComponent')

Динамические компоненты с KeepAlive

По умолчанию при смене компонента предыдущий **уничтожается**. Чтобы сохранить состояние — оберните в <KeepAlive>:

<KeepAlive>
  <component :is="currentTab" />
</KeepAlive>

Теперь при переключении вкладок состояние (данные форм, позиция прокрутки) сохраняется.

<!-- Кэшировать только определённые компоненты -->
<KeepAlive include="TabA,TabB">
  <component :is="currentTab" />
</KeepAlive>

Практический пример: система вкладок

const tabs = [
  { name: 'Профиль',   component: ProfileTab,   icon: '👤' },
  { name: 'Настройки', component: SettingsTab,  icon: '⚙️' },
  { name: 'Уведомления', component: NotifTab,   icon: '🔔' },
]

const activeIndex = ref(0)
const activeComponent = computed(() => tabs[activeIndex.value].component)
<template>
  <nav>
    <button
      v-for="(tab, i) in tabs"
      :key="i"
      :class="{ active: i === activeIndex }"
      @click="activeIndex = i"
    >
      {{ tab.icon }} {{ tab.name }}
    </button>
  </nav>

  <KeepAlive>
    <component :is="activeComponent" />
  </KeepAlive>
</template>

:is с HTML-элементами

:is также работает для динамического выбора нативных HTML-тегов:

<component :is="isHeading ? 'h2' : 'p'">Текст</component>
<component :is="tag" v-bind="attrs">{{ content }}</component>

Примеры

Реализация системы динамических "компонентов" на чистом JavaScript — тот же паттерн, что использует Vue под капотом

// Эмулируем динамические компоненты Vue на чистом JS.
// Каждый "компонент" — объект с методами render и destroy.

// --- "Компоненты" ---
const HomeTab = {
  name: 'HomeTab',
  state: { visitCount: 0 },
  render() {
    this.state.visitCount++
    return `[HomeTab] Главная страница (посещений: ${this.state.visitCount})`
  },
  destroy() { console.log('[HomeTab] уничтожен (состояние потеряно)') }
}

const ProfileTab = {
  name: 'ProfileTab',
  state: { editMode: false, formData: 'Иван Иванов' },
  render() {
    return `[ProfileTab] Профиль: "${this.state.formData}" editMode=${this.state.editMode}`
  },
  destroy() { console.log('[ProfileTab] уничтожен (состояние потеряно)') }
}

const SettingsTab = {
  name: 'SettingsTab',
  state: { theme: 'light', lang: 'ru' },
  render() {
    return `[SettingsTab] Настройки: тема=${this.state.theme}, язык=${this.state.lang}`
  },
  destroy() { console.log('[SettingsTab] уничтожен (состояние потеряно)') }
}

// --- Реестр компонентов (аналог зарегистрированных компонентов Vue) ---
const registry = { HomeTab, ProfileTab, SettingsTab }

// --- Динамический рендерер ---
class DynamicComponent {
  constructor({ keepAlive = false } = {}) {
    this.keepAlive = keepAlive
    this.cache = new Map()     // KeepAlive кэш
    this.current = null
  }

  // Аналог <component :is="name">
  switchTo(nameOrComponent) {
    const comp = typeof nameOrComponent === 'string'
      ? registry[nameOrComponent]   // resolveComponent()
      : nameOrComponent             // объект компонента напрямую

    if (!comp) throw new Error(`Компонент "${nameOrComponent}" не найден`)

    // Уничтожаем текущий (если нет KeepAlive)
    if (this.current && this.current !== comp) {
      if (!this.keepAlive) {
        this.current.destroy()
      } else {
        console.log(`[KeepAlive] "${this.current.name}" сохранён в кэше`)
        this.cache.set(this.current.name, this.current)
      }
    }

    // Восстанавливаем из кэша или используем свежий
    if (this.keepAlive && this.cache.has(comp.name)) {
      this.current = this.cache.get(comp.name)
      console.log(`[KeepAlive] "${comp.name}" восстановлен из кэша`)
    } else {
      this.current = comp
    }

    const output = this.current.render()
    console.log('Рендер:', output)
    return output
  }
}

// === Демо без KeepAlive ===
console.log('=== БЕЗ KeepAlive ===')
const tabs1 = new DynamicComponent({ keepAlive: false })
tabs1.switchTo('HomeTab')
tabs1.switchTo('HomeTab')    // visitCount = 2
tabs1.switchTo('ProfileTab') // HomeTab уничтожен
tabs1.switchTo('HomeTab')    // HomeTab пересоздан → visitCount = 1 (сброс!)

// === Демо с KeepAlive ===
console.log('\n=== С KeepAlive ===')
const tabs2 = new DynamicComponent({ keepAlive: true })
tabs2.switchTo('HomeTab')    // visitCount = 1
tabs2.switchTo('HomeTab')    // visitCount = 2
tabs2.switchTo('ProfileTab') // HomeTab кэшируется
// Меняем состояние ProfileTab
tabs2.current.state.formData = 'Пётр Петров'
tabs2.switchTo('HomeTab')    // восстановлен из кэша → visitCount = 3
tabs2.switchTo('ProfileTab') // восстановлен из кэша → formData сохранён
console.log('ProfileTab formData:', tabs2.current.state.formData)  // 'Пётр Петров'

// === resolveComponent по строке ===
console.log('\n=== resolveComponent ===')
const tabs3 = new DynamicComponent()
const compName = 'SettingsTab'
tabs3.switchTo(compName)
tabs3.current.state.theme = 'dark'
tabs3.switchTo(SettingsTab) // по объекту — тот же компонент