本文主要讲述vue中的异步组件,如果你看完本文相信你应该对vue中的异步组件有着相对深刻的理解,
如果你还不知道vue中异步组件是什么,请参考vue异步组件
异步组件是vue性能优化的一种方式,主要目的是为了解决页面引入很多组件导致打包后的js体积过大,我们知道同样条件下,文件体积越大,请求耗时越长,因此
vue提供了异步组件,当页面中通过异步方式来声明(全局)或者注册(局部)组件时,每个异步组件会被单独打包成一个js文件,每个异步组件对应的js文件在需要时才会被加载。
通过减小打包文件体积的方式实现了性能优化。
组件按需加载通常使用在路由中,但页面上其他组件也可以通过异步组件实现懒加载
App.vue
Foo.vue
{{msg}}
Bar.vue
{{msg}}
Bar组件会被单独打包成0.bundle.js,页面初始化加载不会请求js文件,当点击查看bar组件时,会请求0.bundle.js,并在页面上渲染Bar组件。
这里提供了一种思路从而实现了非路由组件的按需加载。那么路由组件的按需加载使用起来就比较简单,路由组件直接使用异步组件方式引用即可。
普通组件无论时全局组件还是局部组件都是一个对象,而异步组件定义为一个函数或者一个Promise。
异步组件声明有如下三种方式,函数式和Promise以及高级异步组件
函数式
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
import语法
webpack import语法会返回一个Promise
{
...
components: {
Bar: () => import('./Bar.vue')
}
...
}
高级异步组件
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
局部异步组件建议使用import语法,相对函数式声明更加简洁
下面分别从源码角度分析函数式异步,import语法和高级异步组件,主要涉及到createComponent和resolveAsyncComponent两个函数
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
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
}
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
): Class<Component> | void {
const owner = currentRenderingInstance
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)
sync = false
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
createComponent创建组件vnode,如果是异步组件,那么Ctor.cid为undefined,会执行Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
在resolveAsyncComponent方法中,会将当前组件或根实例添加到owners中,也就是异步组件的拥有者。
调用once方法包装异步组件声明中的resolve和reject函数,然后执行factory函数,此时会加载组件也就是执行realComp = require('./my-async-component')
,
realComp(伪代码)为真实组件
请求文件是一个异步过程,此时sync为false,文件请求成功后会执行resolve函数,调用ensureCtor方法将realComp转化我组件构造器。此时sync为false,会调用forceRender方法
forceRender中会遍历owers,调用根实例或组件实例的forceUpdate方法,此时会进入新一轮的渲染,在执行到createComponent方法时,会再次执行resolveAsyncComponent,返回
factory.resolved也就是ensureCtor方法生成的组件构造器,此时会创建真正的组件vnode.最终渲染后的dom会取代之前的注释节点。
const res = factory(resolve, reject)
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
}
}
执行factory函数也就是() => import('./Foo.vue')
返回的res为Promise,isPromise(res)
为true但factory.resolved为undefined,所以此时会执行resolve函数,从而执行forceRender,后续步骤和
函数声明的异步组件相同。
高级异步组件只是在组件的渲染过程中增加了loading,error,和comp,loading对应加载中组件,error为渲染失败时展示组件,如果定义了loading属性,并且delay不为0,那么高级异步组件整个成功的渲染过程不会
被渲染成注释节点,而是LoadingComp=>TargetCompent,即Loading组件和真正渲染的组件MyComponent
非高级异步组件渲染过程: 渲染成注释节点,请求到组件后会调用forceUpdate方法强制根实例或者组件实例更新,此时factory.resolved为Ctor,第二轮更新的过程和普通组件相同
高级异步组件只是功能相对丰富,函数返回的对象具有loading和error属性,增加了异步组件的功能,方便展示请求中和请求失败的效果。其本质和普通异步组件相同,不过是增加了几个分支。