1、从一个例子开始
为了更好的说明,异步组件会在1分钟后被resolve
。
Vue.component('AsyncComp', (resolve) => {
setTimeout(() => {
resolve({
props: {
message: String
},
data() {
return {
msg: "1"
}
},
render(h) {
return h('div', [
h('div', 'hello'),
h('div', this.message),
])
}
})
}, 60 * 1000)
})
new Vue({
el: '#app',
data() {
return {
msg: "1"
}
},
methods: {
change() {
this.msg = String(Math.random())
}
},
render(h) {
return h('div', [
h('button', {
on: {
click: this.change
}
}, 'change'),
h('AsyncComp', {
props:{
message: this.msg
}
})
])
}
})
2、$mount作为入口
在runtime
版的vue入口文件中$mount
是这样定义的。这里提一下compiler
版的vue入口重写了$mount
方法,多了一个将template
编译成render
方法的过程,因为接下来的逻辑render
是一个很重要的东西。
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
3、mountComponent
在mountComponent
里面会创建一个renderWatch
来完成组件vnode
的创建和patch
的逻辑。如果你对vue里面响应式里面发布订阅中涉及到的Observer
、Dep
、Wathcer
还不熟悉的话可以先去了解下。但是不了解也不要紧,因为这篇文章讲述到的内容没有涉及到响应式的部分。在下面的代码中,你只需了解在创建Watcher
对象时将会执行updateComponent
方法。
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
那么updateComponent
又是什么呢?
4、updateComponent
updateComponent
的逻辑很简单,调用vm._render()
创建新的vnode
,在vm._update
内部调用__patch__
对新旧的两个vnode
进行diff
。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
5、创建出的vnode是什么样子的?
vnode
是我们上面例子中定义的render
方法执行返回的。
const { render, _parentVnode } = vm.$options
// ...
vnode = render.call(vm._renderProxy, vm.$createElement)
如果打断点的话,此时应该会进入到我们定义的render
方法中。
- 我们是通过调用render方法的第一个参数
h
来创建vnode
的, 其实h
也就是vm.$createElement
。
在vm.$createElement
内部如果创建的是普通的标签vnode
:
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
如果创建的是自定义组件vnode
, 逻辑会复杂些:
vnode = createComponent(Ctor, data, context, children, tag)
我们只关心异步组件的逻辑, Ctor是通过Ctor = baseCtor.extend(Ctor)
创造出来的构造函数,当然如果是异步组件在这个例子中是不会执行Ctor = baseCtor.extend(Ctor)
的。因为只有当Ctor是一个options对象时才会进行extend
处理得到一个构造函数,每一个构造函数上都有一个cid
属性,感兴趣的朋友可以去看下Vue.extend
的实现。
if (isObject(Ctor)) {
// 通过继承返回一个新的子类构造器
Ctor = baseCtor.extend(Ctor)
}
所以当Ctor.cid不存在时,那么这就是一个异步组件。
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
)
}
}
在异步组件第一次渲染时因为此时还未获取到组件的配置对象options,所以Ctor是undefined
, 此时会返回一个异步组件的占位vnode
。异步组件vnode
特有的两个属性asyncFactory
保存异步函数,asyncMeta
保存从父组件获取的属性数据,子节点等信息, 等异步函数内部被resolve
时重新渲染时会用到。
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
}
创建出来的vnode是什么样的呢?
异步组件未被resolve
之前是渲染成一个空节点的。
创建好vnode
之后就是进行patch
的逻辑, 但是我们现在不关心patch
的逻辑, 从上面创建的vnode
也能看出来这一次patch
是与异部组件无关的, 因为异步组件被一个占位vnode
替代了。
此次patch之后, 页面是这样的:
那异步组件是什么时候渲染的呢? 那当然是异步函数被resolve
的时候, resolve
之后又是怎么做的呢?
让我们看下resolveAsyncComponent
的逻辑就知道了。
6、resolveAsyncComponent
resolveAsyncComponent
的作用是获取异步组件,异步组件在loading、error、resolve
状态下渲染的内容可能是不一样的。由于resolveAsyncComponent
的代码是实现异步组件的关键逻辑, 并且逻辑比较集中我就把所有代码给贴出来了。大概的思路是这样的:
-
如果
factory
(也就是我们定义的异步组件函数)上的factory.error
为真那么表示reject
了,此时如果errorComp
存在,则显示errorComp
组件。 -
factory.resolved
为真表示异步组件已经resolve
,此时可以将异步组件的内容渲染出来了。 -
factory.loading
为真表示异步组件第一次渲染并且还未被resolve
和reject
, 此时页面渲染的是loading
组件。 -
如果上面的条件都不满足, 则执行
const res = factory(resolve, reject)
异步请求组件。 -
resolve之后做的事情:
factory.resolved = ensureCtor(res, baseCtor)
通过Vue.extend(options)
获取到组件构造函数。- 执行
forceRender(true)
, 父组件调用$forceUpdate()
强制刷新父组件, 父组件ptach
的时候就会创建真正的异步组件内容。 - 重新执行第二部的
updateComponent
逻辑。与第一次执行updateComponent
不同的是:- 第一次是同步执行,第二次是异步执行,因为
$forceUpdate()
内部会调用renderWatcher
的update方法。将当前renderWatcher
加入queueWatcher
队列, 而queueWatcher
队列是在nextTick
中执行的。 - 这一次将会从
factory.resolved
上获取到异步组件构造函数。
- 第一次是同步执行,第二次是异步执行,因为
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
): Class<Component> | void {
// 如果异步组件出现错误
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 异步组件获取的到的options会在resolved属性上
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)
}
// loading展示
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
// 第一次使用
if (owner && !isDef(factory.owners)) {
// owners用来保存那些组件实例用到了这个异步组件
const owners = factory.owners = [owner]
let sync = true
let timerLoading = null
let timerTimeout = null
// 当父组件被销毁时,父组件从owners上移除
;(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()
}
// 渲染完成后清空owners
if (renderCompleted) {
owners.length = 0
if (timerLoading !== null) {
clearTimeout(timerLoading)
timerLoading = null
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout)
timerTimeout = null
}
}
}
// 当异步组件被rsolve时
const resolve = once((res: Object | Class<Component>) => {
// cache resolved
// 使用extend的到子类构造函数,并且缓存在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)
// 如果factory执行后返回的是一个对象
if (isObject(res)) {
// 返回的是一个promise对象
/**
function (resolve, reject) {
setTimeout(function () {
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
}
*/
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
/*
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
*/
} else if (isPromise(res.component)) {
res.component.then(resolve, reject)
// error组件
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
// loading组件
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
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!