Специальный тег <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])<component :is> поддерживает все обычные атрибуты и директивы:
<component
:is="currentComponent"
:user="user"
:config="config"
@action="handleAction"
/>Атрибут :is принимает два вида значений:
// 1. Строка — имя зарегистрированного компонента
const currentTab = ref('MyComponent') // должен быть зарегистрирован глобально
// 2. Объект компонента — импортированный напрямую (рекомендуется)
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const current = ref(HomeView)Когда имя компонента известно только как строка, используйте resolveComponent():
import { resolveComponent, h } from 'vue'
// Внутри setup() или render функции
const MyComp = resolveComponent('MyRegisteredComponent')По умолчанию при смене компонента предыдущий **уничтожается**. Чтобы сохранить состояние — оберните в <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-тегов:
<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) // по объекту — тот же компонент
Специальный тег <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])<component :is> поддерживает все обычные атрибуты и директивы:
<component
:is="currentComponent"
:user="user"
:config="config"
@action="handleAction"
/>Атрибут :is принимает два вида значений:
// 1. Строка — имя зарегистрированного компонента
const currentTab = ref('MyComponent') // должен быть зарегистрирован глобально
// 2. Объект компонента — импортированный напрямую (рекомендуется)
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const current = ref(HomeView)Когда имя компонента известно только как строка, используйте resolveComponent():
import { resolveComponent, h } from 'vue'
// Внутри setup() или render функции
const MyComp = resolveComponent('MyRegisteredComponent')По умолчанию при смене компонента предыдущий **уничтожается**. Чтобы сохранить состояние — оберните в <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-тегов:
<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) // по объекту — тот же компонент
Реализуй класс `ComponentRouter` — упрощённый динамический рендерер компонентов. Конструктор принимает объект-реестр `{ name: component }`. Метод `register(name, component)` добавляет компонент в реестр. Метод `show(name)` переключает активный компонент, вызывает `component.onMount()` если он есть, и вызывает `component.onUnmount()` на предыдущем перед переключением. Метод `getCurrent()` возвращает текущий активный компонент.
В методе show: сначала проверь this.current && this.current.onUnmount — вызови onUnmount. Затем найди новый компонент: const comp = this.registry[name]. Если comp не найден — throw new Error. Потом this.current = comp, и если comp.onMount — вызови comp.onMount().
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке