工厂函数
写法:
这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
执行流程:
代码:
// main.js
import Vue from "vue";
import App from "./App.vue";
Vue.component("MyComponent", function(resolve) {
require(["./components/myComponent.vue"], resolve);
});
new Vue({
render: (h) => h(App),
}).$mount("#app");
// App.vue
<template>
<div id="app">
<button @click="isShow = true">请点击!</button>
<my-component v-if="isShow"></my-component>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
isShow: false
};
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// MyComponent.vue
<template>
<h1>我是MyComponent组件</h1>
</template>
<script>
export default {
name: "MyComponent"
};
</script>
<style scoped>
</style>
一、注册全局异步组件
执行Vue.component()注册全局组件时,会调用以下代码:
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
// forEach ASSET_TYPES数组,创建三个全局方法:Vue.component()、Vue.directive()、Vue.filter()
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (
id,
definition
) {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id);
}
// 虽然此时type是 component 但是 definition是一个工厂函数不是对象
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition };
}
// 将这个 工厂函数 添加到 Vue.options.components对象中,id 是组件名(MyComponent)
this.options[type + 's'][id] = definition;
// 返回这个工厂函数
return definition
}
};
});
二、首次渲染
new Vue({
render: (h) => h(App),
}).$mount("#app");
执行new Vue()的初始化后,会进行$mount挂载
挂载阶段大概流程:
-
判断有没有用户提供render函数,没有render就会进入编译阶段(不细说),接着进入
mountComponent函数 -
mountComponent函数,主要做了以下几件事:-
创建一个渲染watcher,收集updateComponent函数中的依赖(这个函数执行阶段使用到的变量都会收集到此渲染watcher中,一但发生变化就会通知渲染watcher重新渲染)
updateComponent = function () { vm._update(vm._render(), hydrating); }; -
执行
vm._render()根据render函数生成App组件的组件节点// App组件的占位符节点 vnode: { child: undefined tag: "vue-component-1-App" data: { on: undefined hook: init: ƒ init(vnode, hydrating) prepatch: ƒ prepatch(oldVnode, vnode) insert: ƒ insert(vnode) destroy: ƒ destroy(vnode) } children: undefined text: undefined elm: undefined ns: undefined context: Vue {_uid: 0, _isVue: true, $options: {…}, …} fnContext: undefined fnOptions: undefined fnScopeId: undefined key: undefined componentOptions: { Ctor: ƒ VueComponent(options) // app的组件构造器 cid为1 propsData: undefined listeners: undefined tag: undefined children: undefined } componentInstance: undefined parent: undefined raw: false isStatic: false isRootInsert: true isComment: false isCloned: false isOnce: false asyncFactory: undefined asyncMeta: undefined isAsyncPlaceholder: false } -
执行
vm._update函数,进入patch阶段,patch方法会调用createElm函数试图创建该vnode的真实vnode。由于此vnode 是一个组件vnode,会执行createComponent函数。 -
createComponent函数会,通过componentVNodeHooks中的init方法,根据此组件vnode创建app组件实例,并赋值到componentInstance。 -
执行app组件实例的mount方法,编译app组件的template模板生成该组件的render渲染函数,执行渲染函数生成该组件的渲染vnode。
// app组件的渲染函数 var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c( "div", { attrs: { id: "app" } }, [ _c( "button", { on: { click: function($event) { _vm.isShow = true } } }, [_vm._v("请点击!")] ), // 这里不会执行 isShow为false,所以忽略 _vm.isShow ? _c("my-component") : _vm._e() ], 1 ) }// app组件的渲染vnode vnode: { child: undefined tag: "div" data:{ attrs: {id: "app"} } children: [ 0: VNode {tag: "button", data: {…}, children: Array(1), …} 1: VNode {tag: undefined, data: undefined, children: undefined, …} ] text: undefined elm: undefined ns: undefined context: VueComponent {_uid: 1, _isVue: true, $options: {…}, _self: VueComponent, …} fnContext: undefined fnOptions: undefined fnScopeId: undefined key: undefined componentOptions: undefined componentInstance: undefined parent: undefined raw: false isStatic: false isRootInsert: true isComment: false isCloned: false isOnce: false asyncFactory: undefined asyncMeta: undefined isAsyncPlaceholder: false }- 再经过
_update=》patch等方法,将最终生成的DOM节点添加到app组件实例的$el上 - 回到
createComponent,执行initComponent方法,将app组件实例上的$el(真实dom)赋值给 该组件占位符vnode的elm,并将vnode.elm插入父组件(body节点中),至此首次渲染结束
-
三、更新视图
当点击页面中的按钮后,会设置isShow为true。此时vue中isShow数据变化,会通知渲染watcher重新渲染页面。
将此渲染watcher加入异步更新队列,nextTick后根据队列中的先后顺序执行任务。
执行updateComponent方法:
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
ps:(此时的watcher是app组件的渲染watcher,所以会通知app组件实例重新渲染,不会通知根实例)
渲染函数是不变的
这里会重新执行渲染函数生成vnode:
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
_c(
"button",
{
on: {
click: function($event) {
_vm.isShow = true
}
}
},
[_vm._v("请点击!")]
),
// isShow发生变化会执行啦
_vm.isShow ? _c("my-component") : _vm._e()
],
1
)
}
四、_c("my-component")(第一次)
- 执行
_createElement方法
function _createElement (
context, // app组件实例
tag, // "my-component"
data,
children,
normalizationType
) {
var vnode, ns;
if (typeof tag === 'string') {
var Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
if (config.isReservedTag(tag)) {
... // 无关代码省略
);
// 第一步 进入 resolvAsset 获取组件
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// 此时Ctor已经为 工厂函数,跳转第二步 createComponent过程
// 从第二步返回过来,vnode 为一个异步注释节点
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);
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) { applyNS(vnode, ns); }
if (isDef(data)) { registerDeepBindings(data); }
// 返回这个注释节点
return vnode
} else {
return createEmptyVNode()
}
}
第一步=》resolveAsset
获取全局定义的组件,将并工厂函数赋值给Ctor。
function resolveAsset (
options, // app组件实例的$optionss
type,
id,
warnMissing
) {
var assets = options[type];
// check local registration variations first
if (hasOwn(assets, id)) { return assets[id] }
var camelizedId = camelize(id);
if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
var PascalCaseId = capitalize(camelizedId);
if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
// fallback to prototype chain
var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
warn(
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
options
);
}
// res 就是工厂函数
/*
function(resolve) {
require(["./components/myComponent.vue"], resolve);
}
*/
return res
}
第二步=》createComponent
function createComponent (
Ctor, // component的工厂函数
data, // undefined
context, // app组件实例
children, // undefined
tag // "my-component"
) {
// baseCtor = Vue构造器 cid:0
var baseCtor = context.$options._base;
// Ctor 不是对象,if不会执行
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 异步组件
var asyncFactory;
if (isUndef(Ctor.cid)) {
// 将 异步组件工厂函数赋值给 asyncFactory
asyncFactory = Ctor;
// 调用 resolveAsyncComponent函数,获取构造器 跳转第三步
Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
// 执行完 第三步 Ctor = undefined
if (Ctor === undefined) {
// 返回一个异步的注释节点占位符,回到_createElement
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
第三步=》resolveAsyncComponent
function resolveAsyncComponent (
factory, // 工厂函数
baseCtor // Vue cid:0
) {
if (isDef(factory.resolved)) {
return factory.resolved
}
// currentRenderingInstance 是在_render中设置的 当前是 app组件实例
var owner = currentRenderingInstance;
// 当前不满足
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner);
}
// 当前满足
if (owner && !isDef(factory.owners)) {
// 给当前工厂函数添加 owners 属性 是一个数组,并将当前 app组件实例添加进去
var owners = factory.owners = [owner];
// 设置 sync 值为ture
var sync = true;
var timerLoading = null;
var timerTimeout = null
// 给当前 app组件实例添加一个“hook:destroyed”监听器,如果被$emit 就将owner从 当前owners中移除
// 当前 app组件 _events 多了一个事件 “hook:destroyed”
;(owner).$on('hook:destroyed', function () { return remove(owners, owner); });
var forceRender = function (renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
(owners[i]).$forceUpdate();
}
if (renderCompleted) {
owners.length = 0;
if (timerLoading !== null) {
clearTimeout(timerLoading);
timerLoading = null;
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout);
timerTimeout = null;
}
}
};
var resolve = once(function (res) {
// 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;
}
});
var reject = once(function (reason) {
// ...
});
// 执行工厂函数 并传入两个函数作为参数 ,此时会调到工厂函数并执行,工厂函数中require是异步的,所以执行完又会跳到这里,此时res=undefined
// require(["./components/myComponent.vue"], resolve);
var res = factory(resolve, reject);
sync = false;
// return undefined
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
五、_update
isShow状态改变后,第一次通过_render()异步加载组件时,异步组件会先返回一个注释节点,然后经过_update的patch到视图中,此时异步组件还未加载到视图中,先用一个注释节点顶替。等到同步流程结束,会执行异步函数的回调,此时就是resolve回调:
require(["./components/myComponent.vue"], resolve);
六、resolve回调
once方法,确保只执行一次:
function once (fn) {
var called = false;
return function () {
if (!called) {
called = true;
// arguments是一个异步组件选项
/* arguments: Arguments(1)
0: Module
default:
beforeCreate: [ƒ]
beforeDestroy: [ƒ]
name: "MyComponent"
render: ƒ ()
staticRenderFns: []
__file: "src/components/myComponent.vue"
_compiled: true
_scopeId: "data-v-717ad35e"
*/
fn.apply(this, arguments);
}
}
}
var resolve = once(function (res) {
// ensureCtor (myComponent 组件选项 , Vue)
// ensureCtor 返回子组件构造器并缓存resolved
factory.resolved = ensureCtor(res, baseCtor);
// 此时 sync=false
if (!sync) {
// 强制重新渲染
forceRender(true);
} else {
owners.length = 0;
}
});
ensureCtor:
function ensureCtor (comp, base) {
if (
comp.__esModule ||
(hasSymbol && comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default;
}
// 更具这个组件选项,扩展子组件构造器
return isObject(comp)
? base.extend(comp)
: comp
}
七、forceRender强制渲染
var forceRender = function (renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
// 调用 app组件的强制渲染,vm._watcher.update() 异步渲染
(owners[i]).$forceUpdate();
}
}
八、_c("my-component")第二次
function createComponent (
Ctor, // component的工厂函数
data, // undefined
context, // app组件实例
children, // undefined
tag // "my-component"
) {
var baseCtor = context.$options._base;
var asyncFactory;
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor;
// 第一步 获取异步组件
// 现在可以获取子组件构造器
Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
}
data = data || {};
var listeners = data.on;
data.on = data.nativeOn;
// 给data中添加相关hook
installComponentHooks(data);
// return a placeholder vnode
var name = Ctor.options.name || tag;
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
// 返回 异步获取的组件vnode
return vnode
}
resolveAsyncComponent
function resolveAsyncComponent (
factory, // 工厂函数
baseCtor // Vue cid:0
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 此时 已经有了resolved
if (isDef(factory.resolved)) {
// 直接返回这个mycomponent构造器
return factory.resolved
}
九、_update
获得了APP组件的渲染节点,进入patch流程,创建mycomponent组件渲染节点。