В этой статье Pogo представит встроенные компоненты Vue 3 —component, роль этого компонента заключается в отображении «метакомпонента» как динамического компонента. Неважно, если вы ничего не знаете о динамических компонентах, в этой статье брат Абао представит применение динамических компонентов на конкретных примерах. Поскольку существует определенная связь между динамическими компонентами и регистрацией компонентов, чтобы каждый мог лучше понять внутренние принципы динамических компонентов, брат Абао сначала представит соответствующие знания о регистрации компонентов.
1. Регистрация компонента
1.1 Глобальная регистрация
В Vue 3.0 с помощьюappобъектcomponentметоды для простой регистрации или извлечения глобальных компонентов.componentМетод поддерживает два параметра:
- имя: имя компонента;
- компонент: объект определения компонента.
Далее рассмотрим простой пример:
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
<script>
const { createApp } = Vue
const app = createApp({}); // ①
app.component('component-a', { // ②
template: "<p>我是组件A</p>"
});
app.component('component-b', {
template: "<p>我是组件B</p>"
});
app.component('component-c', {
template: "<p>我是组件C</p>"
});
app.mount('#app') // ③
</script>
В приведенном выше коде мы передаемapp.componentМетоды зарегистрировали три компонента, которые зарегистрированы глобально.. Код этого примера относительно прост и в основном состоит из 3 шагов:.其中创建App
Следуйте «Дороге полного совершенствования», чтобы прочитать 4 оригинальных бесплатных электронных книги (загружено более 30 000) и9 Учебные пособия по Vue 3 Advanced Series.
1.2 Процесс регистрации глобальных компонентов
В приведенном выше примере мы используемappобъектcomponentспособ регистрации глобального компонента:
app.component('component-a', {
template: "<p>我是组件A</p>"
});
Конечно, в дополнение к регистрации глобальных компонентов мы также можем зарегистрировать локальные компоненты, поскольку компоненты также принимаютcomponentsОпции:
const app = Vue.createApp({
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
Следует отметить, что локально зарегистрированный компонент недоступен в своих дочерних компонентах.. Далее продолжим процесс регистрации глобальных компонентов. Для предыдущего примера мы использовалиapp.componentметод определен вruntime-core/src/apiCreateApp.tsВ файле:
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
// 省略部分代码
_context: context,
// 注册或检索全局组件
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) { // 获取name对应的组件
return context.components[name]
}
if (__DEV__ && context.components[name]) { // 重复注册提示
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component // 注册全局组件
return app
},
})
return app
}
}
После успешной регистрации всех компонентов они будут сохранены вcontextобъектcomponentsсвойства, как показано на следующем рисунке:
Как подсказывает названиеcontextобъект контекста представляет приложение, так как же создается объект? На самом деле объект проходит черезcreateAppContextфункция для создания:
const context = createAppContext()
а такжеcreateAppContextфункция определена вruntime-core/src/apiCreateApp.tsВ файле:
// packages/runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {
return {
app: null as any,
config: { // 应用的配置对象
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,
errorHandler: undefined,
warnHandler: undefined
},
mixins: [], // 保存应用内的混入
components: {}, // 保存全局组件的信息
directives: {}, // 保存全局指令的信息
provides: Object.create(null)
}
}
После анализаapp.component方法之后,是不是觉得组件注册的过程还是挺简单的。那么对于已注册的组件,何时会被使用呢? Чтобы ответить на этот вопрос, нам нужно проанализировать еще один шаг —монтирование приложения.
1.3 Процесс установки приложения
Чтобы понять процесс установки приложений более интуитивно, Brother Abao использует инструменты разработчика Chrome.PerformanceПанель вкладок фиксирует основной процесс монтирования приложения:
На изображении выше мы нашли функцию, связанную с компонентомresolveComponent.很明显,该函数用于解析组件,且该函数在renderметод будет вызван. В исходном коде мы находим определение этой функции:
// packages/runtime-core/src/helpers/resolveAssets.ts
const COMPONENTS = 'components'
export function resolveComponent(name: string): ConcreteComponent | string {
return resolveAsset(COMPONENTS, name) || name
}
Как видно из приведенного выше кода, вresolveComponentВнутри функции он будет продолжать вызыватьresolveAssetфункция для выполнения определенных операций синтаксического анализа. в анализеresolveAssetПеред конкретной реализацией функции мыresolveComponentДобавьте точку останова внутри функции, чтобы посмотретьrender«Аромат» метода:
На рисунке выше мы видим работу аналитических компонентов, таких как_resolveComponent("component-a"). Мы уже знали до этого resolveComponentФункция будет продолжать вызыватьresolveAssetфункция, конкретная реализация этой функции выглядит следующим образом:
// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
type: typeof COMPONENTS | typeof DIRECTIVES,
name: string,
warnMissing = true
) {
const instance = currentRenderingInstance || currentInstance
if (instance) {
const Component = instance.type
// 省略大部分处理逻辑
const res =
// 局部注册
// check instance[type] first for components with mixin or extends.
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
// 全局注册
resolve(instance.appContext[type], name)
return res
} else if (__DEV__) {
warn(
`resolve${capitalize(type.slice(0, -1))} ` +
`can only be used in render() or setup().`
)
}
}
Поскольку при регистрации компонентов используется метод глобальной регистрации, будет выполнен процесс синтаксического анализа.resolve(instance.appContext[type], name)заявление, гдеresolveМетоды определяются следующим образом:
// packages/runtime-core/src/helpers/resolveAssets.ts
function resolve(registry: Record<string, any> | undefined, name: string) {
return (
registry &&
(registry[name] ||
registry[camelize(name)] ||
registry[capitalize(camelize(name))])
)
}
После анализа вышеуказанного потока обработки, когда мы разбираемся на глобально зарегистрированных компонентах, мы пройдемresolveФункция объекта компонента зарегистрирована из объекта контекста приложения.
(function anonymous() {
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const {resolveComponent: _resolveComponent, createVNode: _createVNode,
Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock} = _Vue
const _component_component_a = _resolveComponent("component-a")
const _component_component_b = _resolveComponent("component-b")
const _component_component_c = _resolveComponent("component-c")
return (_openBlock(),
_createBlock(_Fragment, null, [
_createVNode(_component_component_a),
_createVNode(_component_component_b),
_createVNode(_component_component_c)], 64))
}
}
})
_createVNodeVNodeузел.然而,关于VNode
2. Динамические компоненты
в Vue 3 дает намcomponentВстроенный компонент, который может отображать «метакомпонент» как динамический компонент. согласно сisЗначение, чтобы определить, какой компонент отображается. еслиisЗначением является строка, которая может быть либо именем тега HTML, либо именем компонента. Соответствующий пример использования выглядит следующим образом:
<!-- 动态组件由 vm 实例的 `componentId` property 控制 -->
<component :is="componentId"></component>
<!-- 也能够渲染注册过的组件或 prop 传入的组件-->
<component :is="$options.components.child"></component>
<!-- 可以通过字符串引用组件 -->
<component :is="condition ? 'FooComponent' : 'BarComponent'"></component>
<!-- 可以用来渲染原生 HTML 元素 -->
<component :is="href ? 'a' : 'span'"></component>
2.1 Типы строк привязки
ВведениеcomponentВстроенные компоненты, возьмем простой пример:
<div id="app">
<button
v-for="tab in tabs"
:key="tab"
@click="currentTab = 'tab-' + tab.toLowerCase()">
{{ tab }}
</button>
<component :is="currentTab"></component>
</div>
<script>
const { createApp } = Vue
const tabs = ['Home', 'My']
const app = createApp({
data() {
return {
tabs,
currentTab: 'tab-' + tabs[0].toLowerCase()
}
},
});
app.component('tab-home', {
template: `<div style="border: 1px solid;">Home component</div>`
})
app.component('tab-my', {
template: `<div style="border: 1px solid;">My component</div>`
})
app.mount('#app')
</script>
В приведенном выше коде мы передаемapp.componentМетод зарегистрирован глобальноtab-homeа такжеtab-myДва компонента. Кроме того, в шаблоне мы использовалиcomponentВстроенные компоненты сборкиisdataобъектcurrentTabcurrentTab
Когда вы видите это, вы думаетеcomponentВстроенные компоненты довольно волшебные.Заинтересованные партнеры продолжают быть с Абао Гэ, раскрывают секрет, стоящий за ним. Давайте воспользуемсяVue 3 Template Explorerонлайн-инструменты, посмотрите<component :is="currentTab"></component>Результат компиляции шаблона:
const _Vue = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
with (_ctx) {
const { resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock,
createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab)))
}
}
Посмотрев на сгенерированную функцию рендеринга, мы обнаружилиresolveDynamicComponentфункция, по названию функции мы можем знать, что она используется для разрешения динамических компонентов, она определена вruntime-core/src/helpers/resolveAssets.tsВ файле конкретная реализация выглядит следующим образом:
// packages/runtime-core/src/helpers/resolveAssets.ts
export function resolveDynamicComponent(component: unknown): VNodeTypes {
if (isString(component)) {
return resolveAsset(COMPONENTS, component, false) || component
} else {
// invalid types will fallthrough to createVNode and raise warning
return (component || NULL_DYNAMIC_COMPONENT) as any
}
}
существуетresolveDynamicComponentвнутри функции, еслиcomponentПараметр строкового типа, он вызовет введенный ранееresolveAssetметод разбора компонента:
// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
type: typeof COMPONENTS | typeof DIRECTIVES,
name: string,
warnMissing = true
) {
const instance = currentRenderingInstance || currentInstance
if (instance) {
const Component = instance.type
// 省略大部分处理逻辑
const res =
// 局部注册
// check instance[type] first for components with mixin or extends.
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
// 全局注册
resolve(instance.appContext[type], name)
return res
}
}
В предыдущем примере компонент зарегистрирован глобально, поэтому процесс синтаксического анализа начнется сapp.contextобъект контекстаcomponentsПолучите соответствующий компонент из свойства. когдаcurrentTabкогда происходят изменения,resolveAssetФункция возвращает различные компоненты для выполнения функций динамического компонента. Кроме того, еслиresolveAssetФункция не получит соответствующий компонент, вернет текущийcomponentзначение параметра. НапримерresolveDynamicComponent('div')вернусь'div'нить.
// packages/runtime-core/src/helpers/resolveAssets.ts
export const NULL_DYNAMIC_COMPONENT = Symbol()
export function resolveDynamicComponent(component: unknown): VNodeTypes {
if (isString(component)) {
return resolveAsset(COMPONENTS, component, false) || component
} else {
return (component || NULL_DYNAMIC_COMPONENT) as any
}
}
Осторожный маленький партнер может также заметить вresolveDynamicComponentcomponentcomponent || NULL_DYNAMIC_COMPONENTNULL_DYNAMIC_COMPONENT
2.2 Тип объекта привязки
Поняв вышеизложенное, давайте повторно реализуем предыдущую динамическую функцию Tab:
<div id="app">
<button
v-for="tab in tabs"
:key="tab"
@click="currentTab = tab">
{{ tab.name }}
</button>
<component :is="currentTab.component"></component>
</div>
<script>
const { createApp } = Vue
const tabs = [
{
name: 'Home',
component: {
template: `<div style="border: 1px solid;">Home component</div>`
}
},
{
name: 'My',
component: {
template: `<div style="border: 1px solid;">My component</div>`
}
}]
const app = createApp({
data() {
return {
tabs,
currentTab: tabs[0]
}
},
});
app.mount('#app')
</script>
В приведенном выше примереcomponentвстроенные компонентыisСвойства связаныcurrentTabобъектcomponentсвойство, значением которого является объект. Динамически обновляется, когда пользователь нажимает кнопку Tab.currentTabзначение, в результате чегоcurrentTab.componentЗначение также изменяется, реализуя таким образом функцию динамического переключения компонентов. Следует отметить, что при каждом переключении динамические компоненты будут создаваться заново. Но в некоторых сценариях вы захотите сохранить состояние этих компонентов, чтобы избежать проблем с производительностью, вызванных повторяющимися повторными рендерингами.
Для этой проблемы мы можем использовать другой встроенный компонент Vue 3 —keep-alive, обертывающий динамический компонент. Например:
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
keep-aliveОсновная цель встроенных компонентов — сохранить состояние компонента или избежать повторного рендеринга.При его использовании для переноса динамических компонентов неактивные экземпляры компонентов кэшируются, а не уничтожаются. оkeep-aliveВнутренний принцип работы компонента, брат А Бао напишет специальную статью, чтобы проанализировать его позже, и друзья, которые заинтересованы в этом, не забудьте обратить вниманиеVue 3.0 РасширенныйСерия лет.
3. Брату А Бао есть что сказать
3.1 Помимо встроенных компонентов компонента, какие еще существуют встроенные компоненты?
В Vue 3 в дополнение к введению этой статьиcomponentа такжеkeep-alivetransition,transition-group,slotа такжеteleport
3.2 В чем разница между регистрацией глобального компонента и локального компонента?
Зарегистрировать глобальные компоненты
const { createApp, h } = Vue
const app = createApp({});
app.component('component-a', {
template: "<p>我是组件A</p>"
});
использоватьapp.componentГлобальный компонент, зарегистрированный методом, сохраняется вappв объекте контекста объекта приложения. И через компонентный объектcomponentsЛокальные компоненты, зарегистрированные со свойствами, сохраняются в экземпляре компонента.
Зарегистрировать локальные компоненты
const { createApp, h } = Vue
const app = createApp({});
const componentA = () => h('div', '我是组件A');
app.component('component-b', {
components: {
'component-a': componentA
},
template: `<div>
我是组件B,内部使用了组件A
<component-a></component-a>
</div>`
})
Разрешить глобально зарегистрированные и локально зарегистрированные компоненты
// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
type: typeof COMPONENTS | typeof DIRECTIVES,
name: string,
warnMissing = true
) {
const instance = currentRenderingInstance || currentInstance
if (instance) {
const Component = instance.type
// 省略大部分处理逻辑
const res =
// 局部注册
// check instance[type] first for components with mixin or extends.
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
// 全局注册
resolve(instance.appContext[type], name)
return res
}
}
3.3 Могут ли динамические компоненты связывать другие свойства?
componentВстроенные компоненты в дополнение к поддержкеisВ дополнение к привязке также поддерживаются другие привязки свойств и привязки событий:
<component :is="currentTab.component" :name="name" @click="sayHi"></component>
Здесь Апоге используетVue 3 Template Explorer
const _Vue = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
with (_ctx) {
const { resolveDynamicComponent: _resolveDynamicComponent,
openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab.component), {
name: name,
onClick: sayHi
}, null, 8 /* PROPS */, ["name", "onClick"]))
}
}
Соблюдайте приведенную выше функцию рендеринга, в дополнение кisпривязка преобразуется в_resolveDynamicComponentpropsобъект.
Следуйте «Дороге полного совершенствования», чтобы прочитать 4 оригинальных бесплатных электронных книги (загружено более 30 000) и9 Учебные пособия по Vue 3 Advanced Series.
4. Справочные ресурсы
Статьи в расширенной серии Vue 3.0 все еще обновляются и были обновлены до девятой статьи.Друзья, которые хотят изучать Vue 3.0 вместе, могут добавить Abaoge WeChat - semlinker.