Эта статья является шестой в серии статей о Vue. Друзья, которые читали этот цикл моих статей, знают, что: статья длинная, и если вы не сможете ее прочитать, то рекомендуется поставить лайк как любимую, а затем дождаться времени, чтобы успокоиться и читать ее не спеша. Группа внешнего обмена: 731175396. Предыдущая статья Порталы следующие
- «Разговор о vue core — глава vdom»
- "Разговор о vue - слоте"
- "Рассказываем о vue - переходе в деталях"
- "Говоря о vue - переходная группа"
- «Разговор о Vue — абстрактные компоненты на практике»
использовалvueдрузья должны знать, что вvueв разработке,componentВезде видно, что по одному в проекте.vue (SFC) Файл, разве это не единственный компонент?
Затем, посколькуcomponentТакое ядро, такое важное, почему бы не рассмотреть его хорошенько?
почему бы нет?
— Лу Синь
1. Создание компонента
Прежде чем мы проанализировалиvdomПри анализе функцииcreateElement, который идентиченcreateComponent, оба используются для созданияvnodeузла, если нормальныйhtmlтег, а затем непосредственно создать экземпляр обычногоvnodeузел, иначе пройтиcreateComponentсоздатьComponentТипvnodeузел
1. создатьЭлемент
Здесь перечислены только разные случаиvnodeКод создания узла
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
2. создатькомпонент
Далее давайте посмотрим наcreateComponent()определение следующим образом
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
- Внутри него первым делом нужно поместить конструктор
Vueназначить переменнойbaseCtor, и пройтиextendустановить параметрCtorрасширять
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
Здесь мы видим$options._base, который на самом деле является конструкторомVue
// src/core/global-api/index.js
Vue.options._base = Vue
// src/core/instance/init.js
// 1. initMixin()
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// 2. initInternalComponent()
const opts = vm.$options = Object.create(vm.constructor.options)
- Затем, затем определите, является ли компонент асинхронным компонентом, функциональным компонентом или абстрактным компонентом. Я проанализирую детали каждого случая позже.
// 异步组件
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
// 函数式组件
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// 抽象组件
if (isTrue(Ctor.options.abstract)) {
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
- Существует также связанная обработка событий в компонентах, которая извлекает прослушиватели событий в компонентах. Он должен быть прослушивателем дочерних компонентов, а не прослушивателем DOM. Поэтому его необходимо заменить на наличие
.nativeПрослушиватель модификаторов, которые можно обрабатывать на этапе исправления родительского компонента.
const listeners = data.on
data.on = data.nativeOn
- Затем установите функцию ловушки компонента. Так и будет
componentVNodeHooksФункция ловушки объединена вdata.hook, тогдаComponentТипvnodeузел вpatchВ процессе будет выполняться соответствующая функция-ловушка.Если функция-ловушка в определенный момент времени уже существует, она будет передана черезmergeHookСлияние функций, то есть последовательное выполнение этих двух функций одновременно.
installComponentHooks(data)
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
const hooksToMerge = Object.keys(componentVNodeHooks)
function mergeHook (f1: any, f2: any): Function {
const merged = (a, b) => {
f1(a, b)
f2(a, b)
}
merged._merged = true
return merged
}
3. Компонент VNodeHooks
надcomponentVNodeHooksЭто несколько функций ловушек, реализованных при инициализации компонента, соответственно.init,prepatch,insert,destroy
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
const mountedNode: any = vnode
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
queueActivatedComponent(componentInstance)
} else {
activateChildComponent(componentInstance, true /* direct */)
}
}
},
destroy (vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */)
}
}
}
}
Далее рассмотрим поближеcomponentVNodeHooksЧто делают четыре хука в нем?
-
init:когдаvnodeдляkeep-aliveКогда компонент существует, есть экземпляр, и он не уничтожается, чтобы предотвратить поток компонента, он выполняется напрямую.prepatch. В противном случае напрямую, выполнивcreateComponentInstanceForVnodeСоздаватьComponentТипvnodeпример, и$mountдействовать -
prepatch: Обновите существующие компоненты до последней версии.vnodeДанные выше, тут и говорить не о чем -
insert:insertфункция ловушки- Во-первых, он определит, был ли экземпляр компонента
mounted, если он не визуализируется, напрямуюcomponentInstanceвыполнить как параметрmountedфункция крючка. - Во-вторых, компонент
keep-aliveВ случае встроенных компонентов. Здесь немного операции, то есть когда она былаmountedкогда пора входитьinsertэтапе, чтобы предотвратитьkeep-aliveТриггер обновления дочернего компонентаactivatedФункция крюка, просто сдавайсяwalking treeмеханизм обновления, но непосредственно в экземпляр компонентаcomponentInstanceбросатьactivatedChildrenв этом массиве. конечно, нетmountedзапускается напрямуюactivatedфункция ловушкиmountedТолько что
- Во-первых, он определит, был ли экземпляр компонента
-
destroy: Операция уничтожения компонента, здесь то же самоеkeep-aliveКомпоненты сделаны совместимыми. если неkeep-aliveкомпоненты, прямое исполнение$destoryУничтожить экземпляр компонента, иначе вызватьdeactivatedФункция крюка для разрушения.
Функции помощника следующим образом с вышеупомянутым
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
return new vnode.componentOptions.Ctor(options)
}
- последний экземпляр
VNode, затем вернуться
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
createComponentВесь процесс таков: сначала строим сначалаVueКонструктор подкласса, затем установите функцию ловушки компонента и, наконец, создайте экземплярVNode, затем вернуться. Многие операции в нем правильныеkeep-aliveВстроенные компоненты обеспечивают большую совместимость. Итак, если вы использовалиkeep-aliveКомпоненты и случилось, чтобы увидеть это, я считаю, что у вас много настроений.
2. Слияние конфигураций
Вообще говоря, при разработке подключаемого модуля или компонента, чтобы обеспечить его настраиваемость и расширяемость, он обычно определяет некоторые конфигурации по умолчанию в себе, а затем хорошо выполняет внутреннюю работу.mergeРабота элемента конфигурации позволяет настроить конфигурацию на этапе ее инициализации.
Конечно,VueТо же самое и в этом дизайне.vueсреда дляoptionsСтратегия слияния На самом деле я также перечислил код выше, в частности, вsrc/core/instance/init.js(Здесь я храню только соответствующий код).
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// ...
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// ...
}
}
Видно, что есть две стратегии слияния. один дляComponentкомпонент, выполнитьinitInternalComponentВыполните слияние конфигурации внутреннего компонента, один из которых не является компонентом, непосредственно черезmergeOptionsВыполните слияние конфигурации.
1. нормальное слияние
прямо здесьresolveConstructorOptions(vm.constructor)возвращаемое значение иoptionsобъединить
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
Давайте сначала посмотримVue.optionsОпределение
// src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {
// ...
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// ...
}
// src/shared/constants.js
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
Далее давайте посмотрим наmergeOptionsлогика: этоvueОдна из основных стратегий слияния, ее основная функция заключается в объединенииparantа такжеchildСлить стратегию, а потом вернуть новый объект, код вsrc/core/util/options.jsсередина.
- сначала будет
childнадprops,inject,directivesпровестиobject formatОперация (конкретная логика может быть исследована самостоятельно, в основном для выполненияobjectоперация преобразования) - подобно
child._baseне существует, пройдитеchild.extendsа такжеchild.mixins, объединить его вparentначальство - траверс
parent,передачаmergeFieldобъединить в переменнуюoptionsначальство - траверс
child,подобноchildимеютparentсвойства, которые не существуют, вызовитеmergeFieldобъединить это свойство вoptionsначальство
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
vueВ дополнение кoptionsВ дополнение к слиянию существует множество стратегий слияния, если вы заинтересованы, вы можете перейти кsrc/core/util/options.jsОзнакомьтесь с исследованием в
2. слияние компонентов
в анализеcreateComponentКогда мы узнали, что конструктор компонента передаетсяVue.extendправильноVueНаследование, код выглядит следующим образом
// src/core/global-api/index.js
Vue.options._base = Vue
// src/core/vdom/create-component.js
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
мы приходим сноваVue.extend, который определен вsrc/core/global-api/extend.jsin (сохраняется только критическая логика), выполняетсяmergeOptions()БудуSuper.options,Прямо сейчасVue.optionsОбъединить вSub.optionsсередина
export function initExtend (Vue: GlobalAPI) {
// ...
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
// ...
const Sub = function VueComponent (options) {
this._init(options)
}
// ...
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// ...
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// ...
return Sub
}
}
затем вcomponentVNodeHooksизinitВ функции хука, то есть на этапе инициализации дочернего компонента, он будет выполнятьcreateComponentInstanceForVnodeИнициализировать экземпляр компонента.createComponentInstanceForVnodeв функцииvnode.componentOptions.CtorНа самом деле указывает на вершинуVue.extendвернулся вSub, поэтому выполнитеnewбудет выполняться при работеthis._init(options),Прямо сейчасVue._init(options)операции, а потомуoptions._isComponentопределяется какtrue, поэтому сразу переходите кinitInternalComponentдействовать
// componentVNodeHooks init()
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// createComponentInstanceForVnode()
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// ...
return new vnode.componentOptions.Ctor(options)
}
initInternalComponentЯ просто сделал несколько простых присваиваний объектов, подробностей разбирать не буду, код такой:
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Говоря об этом, некоторые друзья могут быть немного смущены, я приведу пример для иллюстрации.
<template>
<div class="hello">
{{ msg }}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
created () {
console.log('this is child')
}
}
</script>
Затем вызовите его в родительском компоненте
<template>
<div class="home">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'home',
components: {
HelloWorld
},
created () {
console.log('this is parent')
}
}
</script>
Пройдя описанную выше стратегию слияния,vm.$optionsЗначение примерно следующее
vm.$options = {
parent: VueComponent, // 父组件实例
propsData: {
msg: 'Welcome to Your Vue.js App'
},
_componentTag: 'HelloWorld',
_parentListeners: undefined,
_parentVnode: VNode, // 父节点 vnode 实例
_propKeys: ['msg'],
_renderChildren: undefined,
__proto__: {
components: {
HelloWorld: function VueComponent(options) {}
},
directives: {},
filters: {},
_base: function Vue(options) {},
_Ctor: {},
created: [
function created() {
console.log('this is parent')
},
function created() {
console.log('this is child')
}
]
}
}
3. Асинхронные компоненты
В приведенном выше анализеcreateComponentКогда мы оставляем несколько особых случаев, нет анализа, один из которых является случай асинхронных компонентов. Это сцена, которая, когдаCtor.cidЕсли он не определен, переходим непосредственно к процессу создания асинхронных компонентов Конкретный код выглядит следующим образом
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
Прежде чем приступить к конкретному анализу, давайте сначала посмотрим на разницу между использованием асинхронных компонентов и использованием обычных компонентов на официальных примерах.
// 普通组件
Vue.component('my-component-name', {
// ... options ...
})
// 异步组件
Vue.component('async-webpack-example', function (resolve, reject) {
// 这个特殊的 require 语法
// 将指示 webpack 自动将构建后的代码,
// 拆分到不同的 bundle 中,然后通过 Ajax 请求加载。
require(['./my-async-component'], resolve)
})
В примереVueОбычный компонент — это объект, а асинхронный компонент — это фабричная функция, которая получает 2 параметра:resolveФункция обратного вызова используется для получения объекта, определенного компонентом, с сервера, а другойrejectФункция обратного вызова для индикации сбоя загрузки. В дополнение к указанным выше методам записи асинхронные компоненты также поддерживают следующие два метода записи.
// Promise 异步组件
Vue.component(
'async-webpack-example',
// `import` 函数返回一个 Promise.
() => import('./my-async-component')
)
// 高级异步组件
const AsyncComponent = () => ({
// 加载组件(最终应该返回一个 Promise)
component: import('./MyComponent.vue'),
// 异步组件加载中(loading),展示为此组件
loading: LoadingComponent,
// 加载失败,展示为此组件
error: ErrorComponent,
// 展示 loading 组件之前的延迟时间。默认:200ms。
delay: 200,
// 如果提供 timeout,并且加载用时超过此 timeout,
// 则展示错误组件。默认:Infinity。
timeout: 3000
})
Vue.component('async-component', AsyncComponent)
1. разрешить асинхронный компонент
resolveAsyncComponentОсновная функция заключается в поддержке трех упомянутых выше методов создания асинхронных компонентов.Конкретные коды следующие:
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
): Class<Component> | void {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved
}
const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (owner && !isDef(factory.owners)) {
const owners = factory.owners = [owner]
let sync = true
let timerLoading = null
let timerTimeout = null
;(owner: any).$on('hook:destroyed', () => remove(owners, owner))
const forceRender = (renderCompleted: boolean) => {
for (let i = 0, l = owners.length; i < l; i++) {
(owners[i]: any).$forceUpdate()
}
if (renderCompleted) {
owners.length = 0
if (timerLoading !== null) {
clearTimeout(timerLoading)
timerLoading = null
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout)
timerTimeout = null
}
}
}
const resolve = once((res: Object | Class<Component>) => {
// cache resolved
factory.resolved = ensureCtor(res, baseCtor)
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
forceRender(true)
} else {
owners.length = 0
}
})
const reject = once(reason => {
process.env.NODE_ENV !== 'production' && warn(
`Failed to resolve async component: ${String(factory)}` +
(reason ? `\nReason: ${reason}` : '')
)
if (isDef(factory.errorComp)) {
factory.error = true
forceRender(true)
}
})
const res = factory(resolve, reject)
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isPromise(res.component)) {
res.component.then(resolve, reject)
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
factory.loading = true
} else {
timerLoading = setTimeout(() => {
timerLoading = null
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender(false)
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
timerTimeout = setTimeout(() => {
timerTimeout = null
if (isUndef(factory.resolved)) {
reject(
process.env.NODE_ENV !== 'production'
? `timeout (${res.timeout}ms)`
: null
)
}
}, res.timeout)
}
}
}
sync = false
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
Во-первых, давайте посмотрим, как загружаются асинхронные компоненты. Здесь мы пропускаем сначалаresolveAsyncComponentНачните с продвинутых асинхронных компонентов, о которых мы упоминали ранее.
в анализеresolveAsyncComponentПрежде чем создавать логику для асинхронных компонентов, давайте взглянем на некоторые основные методы, которые будут в ней использоваться.
-
forceRender: принудительно выполнить повторный рендеринг компонента, затемrenderКогда закончите, очистите текущий экземпляр рендеринга в фабричной функции.owners, КстатиtimerLoadingа такжеtimerTimeoutЧисто.$forceUpdate:передачаwatcherизupdateметод повторного рендеринга компонента. правильноvueОбычно только изменения данных вызывают повторную визуализацию представления, в то время как асинхронный компонент не изменяется в процессе загрузки данных, тогда на этот раз сборка не будет повторно визуализироваться, поэтому вы можете выполнить$forceUpdateПринудительный повторный рендеринг компонента
const forceRender = (renderCompleted: boolean) => {
for (let i = 0, l = owners.length; i < l; i++) {
(owners[i]: any).$forceUpdate()
}
if (renderCompleted) {
owners.length = 0
if (timerLoading !== null) {
clearTimeout(timerLoading)
timerLoading = null
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout)
timerTimeout = null
}
}
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
-
once: использует замыкания и идентификационную переменнуюcalledГарантирует, что функция, которую он обертывает, будет выполнена только один раз.
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
-
resolve:внутреннийresolveфункция, которая будет выполнена первойensureCtorи верните его какfactoryизresolvedстоимость. Немедленно, еслиsyncАсинхронная переменнаяfalse, затем выполнить напрямуюforceRenderПринудительный повторный рендеринг компонента, в противном случае пустойownersensureCtorЭто функция, определенная для обеспечения того, чтобы объект компонента, определенный для асинхронного компонента, мог быть найден. Если окажется, что это обычный объект, прямо передайтеVue.extendПреобразуйте его в конструктор компонента
const resolve = once((res: Object | Class<Component>) => {
factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {
forceRender(true)
} else {
owners.length = 0
}
})
function ensureCtor (comp: any, base) {
if (
comp.__esModule ||
(hasSymbol && comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default
}
return isObject(comp)
? base.extend(comp)
: comp
}
-
reject:внутреннийrejectФункция, выполняемая, когда асинхронный компонент не загружается
const reject = once(reason => {
process.env.NODE_ENV !== 'production' && warn(
`Failed to resolve async component: ${String(factory)}` +
(reason ? `\nReason: ${reason}` : '')
)
if (isDef(factory.errorComp)) {
factory.error = true
forceRender(true)
}
})
После прочтения основных методов давайте посмотрим, как создаются асинхронные компоненты.
- мы начинаем с
resolveAsyncComponentОпределение метода знает, что метод получает 2 параметра, одинfactoryзаводская функция, одинbaseCtor,Прямо сейчасVue. - Затем, когда текущий экземпляр рендеринга существует, и
factory.ownersприсутствует в том случае, если компонент входитpendingэтап, напрямую перетащите текущий экземпляр вfactory.ownersсередина. - Однако при инициализации асинхронного компонента
factoryне будет иметьownersКапля, то что мне делать в это время? Это очень просто, просто выполнить егоfactoryзаводскую функцию и поместите внутренне определеннуюresolveа такжеrejectв качестве аргумента, поэтому мы можем напрямую передатьresolveа такжеrejectСделайте что-нибудь, эта логика также является логикой, поддерживаемой обычными асинхронными компонентами, соответствующие коды следующие
const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
if (owner && !isDef(factory.owners)) {
const owners = factory.owners = [owner]
let sync = true
;(owner: any).$on('hook:destroyed', () => remove(owners, owner))
const forceRender = (renderCompleted: boolean) => {
// ...
}
const resolve = once((res: Object | Class<Component>) => {
// ...
})
const reject = once(reason => {
// ...
})
const res = factory(resolve, reject)
// ...
}
-
PromiseАсинхронные компоненты
существуетvueВы можете использовать егоwebpack2++ Способ ES6 для асинхронной загрузки компонентов, как показано ниже
Vue.component(
'async-webpack-example',
// `import` 函数返回一个 Promise.
() => import('./my-async-component')
)
это выполняетсяres = factory(resolve, reject)час,resценностьimport('./my-async-component')Возвращаемое значение , являетсяPromiseобъект. войти послеPromiseЛогика обработки асинхронного компонента, которая выполняется после успешной загрузки асинхронного компонентаresolve, если загрузка не удалась, выполнитьreject
const res = factory(resolve, reject)
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
}
- Расширенные асинхронные компоненты
На самом деле так называемые продвинутые здесь на самом делеvueВ версии 2.3.0+ добавлена функция обработки состояния загрузки, то есть пользователю подкидываются некоторые настраиваемые поля, в т.ч.component,loading,error,delay,timeout,вcomponentслужба поддержкиPromiseФорма загрузки асинхронного компонента, код конкретного случая выглядит следующим образом
const AsyncComponent = () => ({
// 加载组件(最终应该返回一个 Promise)
component: import('./MyComponent.vue'),
// 异步组件加载中(loading),展示为此组件
loading: LoadingComponent,
// 加载失败,展示为此组件
error: ErrorComponent,
// 展示 loading 组件之前的延迟时间。默认:200ms。
delay: 200,
// 如果提供 timeout,并且加载用时超过此 timeout,
// 则展示错误组件。默认:Infinity。
timeout: 3000
})
Vue.component('async-component', AsyncComponent)
Анализ банды ХэPromiseЛогика загрузки асинхронных компонентов такая же, если выполнение завершеноres = factory(resolve, reject),res.componentВозвращаемое значениеPromiseЕсли это так, выполните напрямуюthenметод, код выглядит следующим образом
else if (isPromise(res.component)) {
res.component.then(resolve, reject)
}
Затем следует обработка остальных 4 настраиваемых полей.
- Сначала определите, настроен ли он
errorкомпоненты, если таковые имеются, выполняютсяensureCtor(res.error, baseCtor)и присвойте возвращаемое значение непосредственноfactory.errorComp - Аналогично, если входящий
loadingкомпонент, выполнитьensureCtor(res.loading, baseCtor)и присвойте возвращаемое значение непосредственноfactory.loadingComp - Далее, после определения
loadingВ логике компонента, если установленоdelayзначение равно 0, напрямуюfactory.loadingзначение установлено наtrue, иначе задержитесьdelayвоплощать в жизнь,delayНе установлено, задержка по умолчанию 200 мс. - Наконец, если вы установите загруженный компонент
timeoutЕсли время загрузки долгое, если компонент находится вres.timeoutЕсли загрузка не удалась по истечении времени, выполнить напрямуюrejectжеребьевка
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
factory.loading = true
} else {
timerLoading = setTimeout(() => {
timerLoading = null
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender(false)
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
timerTimeout = setTimeout(() => {
timerTimeout = null
if (isUndef(factory.resolved)) {
reject(
process.env.NODE_ENV !== 'production'
? `timeout (${res.timeout}ms)`
: null
)
}
}, res.timeout)
}
Затем, наконец, вынесите решениеfactory.loadingВернуть разные значения, настраиваемые поля сверхуloadingМы знаем, что если пользовательское полеdelayУстановите в 0, это означает, что этот прямой рендерингloadingкомпонент, в противном случае он будет напрямую отложен и выполнен до тех пор, покаforceRenderметод, который вызовет повторную визуализацию компонента для повторного выполненияresolveAsyncComponent
sync = false
return factory.loading
? factory.loadingComp
: factory.resolved
затем мы возвращаемся кresolveAsyncComponentНекоторые операции, которые мы пропустили в начале
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
2. создать асинхронный заполнитель
пара сверхуresolveAsyncComponentИз анализа узнаем, что если это первое исполнениеresolveAsyncComponent, возвращаемое значение будетundefined, конечно ты будешьdelayЗа исключением случаев, когда значение равно 0. чтобы избежатьCtorдляundefinedКогда информация об узле не может быть захвачена, она будет напрямую передаватьсяcreateAsyncPlaceholderСоздание комментарияvnodeNode в качестве заполнителя для асинхронных компонентов, сохраняя при этомvnodeВся необработанная информация об узле. Конкретный код выглядит следующим образом
export function createAsyncPlaceholder (
factory: Function,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag: ?string
): VNode {
const node = createEmptyVNode()
node.asyncFactory = factory
node.asyncMeta = { data, context, children, tag }
return node
}
4. Функциональные компоненты
анализироватьcreateComponentПри создании компонента мы оставили без ответа один вопрос, а именноfunctional component(функциональный компонент), конкретные сценарии следующие
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
Перед разбором, чтобы некоторые друзья не знали, что такое функциональные компоненты, я перечислю два официально поддерживаемых функциональных компонента.
// 方式一 render function
Vue.component('my-component', {
functional: true,
// Props 是可选项
props: {
// ...
},
// 为了弥补缺少的实例
// 我们提供了第二个参数 context 作为上下文
render: function (createElement, context) {
// ...
}
})
// 方式二 template functional
<template functional></template>
Чтобы узнать больше, вы можете перейти непосредственно кофициальная документацияСначала внимательно прочитайте.
Официальное определение функциональных компонентов: компоненты помечены какfunctional, он не имеет состояния и не отвечаетdata, нет экземпляра, т.е. нетthisконтекст.
Теперь давайте приоткроем завесу над функциональными компонентами.
1. создать функциональный компонент
createFunctionalComponentОсновное ядро разделено на три этапа
- Буду
Ctor.optionsсерединаpropsобъединить в новый объектpropsсередина. подобноCtor.optionsсуществуетprops, пройти его напрямуюprops,воплощать в жизньvalidatePropправильноCtor.options.propsПроверьте текущее свойство и скопируйте текущее свойство вprops[key]. подобноCtor.options.propsне определено, будетdataопределено вышеattrsа такжеpropsвыполнивmergePropsфункция объединена в новый объектpropsначальство. - воплощать в жизнь
new FunctionalRenderContextсоздавать экземплярfunctionalконтекст компонента и выполнитьoptionsВверхrenderсоздание экземпляра функцииvnodeузел - созданный
vnodeВыполните специальную операцию клонирования и вернитесь
export function createFunctionalComponent (
Ctor: Class<Component>,
propsData: ?Object,
data: VNodeData,
contextVm: Component,
children: ?Array<VNode>
): VNode | Array<VNode> | void {
const options = Ctor.options
const props = {}
const propOptions = options.props
if (isDef(propOptions)) {
for (const key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || emptyObject)
}
} else {
if (isDef(data.attrs)) mergeProps(props, data.attrs)
if (isDef(data.props)) mergeProps(props, data.props)
}
const renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
)
const vnode = options.render.call(null, renderContext._c, renderContext)
if (vnode instanceof VNode) {
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
} else if (Array.isArray(vnode)) {
const vnodes = normalizeChildren(vnode) || []
const res = new Array(vnodes.length)
for (let i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
}
return res
}
}
Две вспомогательные функции, упомянутые выше, следующие:
-
cloneAndMarkFunctionalResult: Чтобы избежать мультиплексирования узлов,fnContextВ случае несоответствия именованных слотов установите его напрямуюfnContextКлонируйте узел раньше и, наконец, верните клонированный узел.vnode -
mergeProps:propsСтратегия слияния
function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
const clone = cloneVNode(vnode)
clone.fnContext = contextVm
clone.fnOptions = options
if (process.env.NODE_ENV !== 'production') {
(clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext
}
if (data.slot) {
(clone.data || (clone.data = {})).slot = data.slot
}
return clone
}
function mergeProps (to, from) {
for (const key in from) {
to[camelize(key)] = from[key]
}
}
2. Функциональный контекст рендеринга
Из документации мы знаем, что функциональные компоненты поддерживают два способа записи, первый —render functionпуть, другой<template functional>Однофакторный компонентный подход.render functionпуть вcreateFunctionalComponentПоддержка была сделана в обработке, она будет выполняться напрямуюCtor.optionsВверхrenderметод. Отображение конструкторов контекста в функциональных компонентахFunctionalRenderContextсередина справа<template functional>Также поддерживается однофайловый компонентный подход.
- Во-первых, это гарантирует, что функциональные компоненты
createElementФункция может получить уникальный контекст, который будет клонироватьparentприсвоение объекта контекстуvmПеременнаяcontextVm,contextVm._originalназначается какparent, как маркер его контекстуального происхождения. Один из наиболее критических случаев заключается в том, что если входящий контекстvmТакже функциональный контекст, как это сделать? На самом деле, пока_uidСуществующую ситуацию можно переломить, подтолкнув логику,contextVmперениматьparent,parentперениматьparent._originalЭтого достаточно, потому что если вы продолжите смотреть вверх, вы всегда сможете найти существование._uidизparentнет. - Далее функциональная составляющая
data,props,listeners,injectionsи т. д. для обработки поддержки, здесь дляslotsБыл выполнен слой обработки преобразования, который вот-вотnormal slotsобъект, преобразованный вscoped slots - последняя пара
options._scopeIdНаличие или отсутствие сцены делают разныеcreateElementОперация создания узла
export function FunctionalRenderContext (
data: VNodeData,
props: Object,
children: ?Array<VNode>,
parent: Component,
Ctor: Class<Component>
) {
const options = Ctor.options
let contextVm
if (hasOwn(parent, '_uid')) {
contextVm = Object.create(parent)
contextVm._original = parent
} else {
contextVm = parent
parent = parent._original
}
const isCompiled = isTrue(options._compiled)
const needNormalization = !isCompiled
this.data = data
this.props = props
this.children = children
this.parent = parent
this.listeners = data.on || emptyObject
this.injections = resolveInject(options.inject, parent)
this.slots = () => {
if (!this.$slots) {
normalizeScopedSlots(
data.scopedSlots,
this.$slots = resolveSlots(children, parent)
)
}
return this.$slots
}
Object.defineProperty(this, 'scopedSlots', ({
enumerable: true,
get () {
return normalizeScopedSlots(data.scopedSlots, this.slots())
}
}: any))
// support for compiled functional template
if (isCompiled) {
// exposing $options for renderStatic()
this.$options = options
// pre-resolve slots for renderSlot()
this.$slots = this.slots()
this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots)
}
if (options._scopeId) {
this._c = (a, b, c, d) => {
const vnode = createElement(contextVm, a, b, c, d, needNormalization)
if (vnode && !Array.isArray(vnode)) {
vnode.fnScopeId = options._scopeId
vnode.fnContext = parent
}
return vnode
}
} else {
this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)
}
}
5. Абстрактные компоненты
Для абстрактных компонентов я написал несколько статей для их анализа, поэтому я не буду повторять их здесь.Если вы хотите прочитать, вы можете нажать на портал, чтобы прочитать их самостоятельно.
- "Рассказываем о vue - переходе в деталях"
- "Говоря о vue - переходная группа"
- «Разговор о Vue — абстрактные компоненты на практике»
Суммировать
На этом этапе статьи, после крупномасштабного анализа текста, мы имеемvueСоздание китайских компонентов (включая создание асинхронных компонентов, создание функциональных компонентов и создание абстрактных компонентов), функции перехвата компонентов и слияние конфигураций компонентов имеют более полное понимание.
Здесь я также надеюсь, что после понимания этих принципов компонентов вы сможете совместить бизнес с лучшими практиками разработки компонентов в своем собственном бизнесе.Например, лично я использовал личное лучшее решение на мой взгляд - абстрактный компонент, он решает бизнес-болевая точка управления правами очень хорошо
Группа внешнего обмена: 731175396, приглашаем присоединиться