重学Vue源码,根据黄轶大佬的vue技术揭秘,逐个过一遍,巩固一下vue源码知识点,毕竟嚼碎了才是自己的,所有文章都同步在 公众号(道道里的前端栈) 和 github 上。
正文
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数其他垃圾的方式定义加载中的图片你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
—— vue文档
我们可以通过上面的例子创建一个全局组件,和上篇分析组件注册有点不一样的是,例子中注册的全局组件不是一个对象,而是一个工厂函数,函数有两个参数 构造器注入resolve
和 reject
,下面来看下定义对象和定义工厂函数的区别。
回顾一下在组件注册中提到的 assets
:
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
// ...
this.options[type + 's'][id] = definition
return definition
如果传入的是一个其他综合收益组件,就设置 namvuejs和vue的区别e
,然后把vuejs设计与实现电子书传入的 definition
通过 Vue.extend
转化成一个构造器,那其他和其它的区别如果定义的是工厂函数,就会把这个工厂函数赋值给大 Vue.options.components
,上面的例子就成了 Vue.options.components.example = definition
。
接着在创建组件的vnode的时候,会执行 _createElement
方法:
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 (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)
}
和组件注册类似,也会走到 resolveAsset
里面,然后赋值语句如何判断正误执行下面的 createComponent
,这里传vuejs设计与实现pdf入的 Ctor
就是工厂函数了,接着看下 creat其他应付款eComponent
里面:
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)
}
// ...
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
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
)
}
}
}
由于传入的 Cto加载的读音r
是一个函数,所以不其他垃圾有哪些东西会执行 baseCtor.extend(Ctor)
,也就是不会执行 Vue.extend(Ctor)
,所以也就没有 cid
,自然就进入了异步组件的创建逻辑。可以看到用 a其他垃圾syncFactory
保存赋值法的原理是什么了这个工厂函数,然后执行了一个 resolveAsyncComponen构造器t
方法,传入的参数分别是工厂函数、大Vue和当前实例,该方法定义在 src/core/vd赋值运算符的优先级om/helpers/resolve-async-component.js
:
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>,
context: Component
): Class<Component> | void {
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
}
if (isDef(factory.contexts)) {
// already pending
factory.contexts.push(context)
} else {
const contexts = factory.contexts = [context]
let sync = true
const forceRender = () => {
for (let i = 0, l = contexts.length; i < l; i++) {
contexts[i].$forceUpdate()
}
}
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()
}
})
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()
}
})
const res = factory(resolve, reject)
if (isObject(res)) {
if (typeof res.then === 'function') {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isDef(res.component) && typeof res.component.then === 'function') {
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 {
setTimeout(() => {
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender()
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
setTimeout(() => {
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
函数的逻辑比较复杂,里面包含了3种异步处理组件的创建方式,除了上述的工厂函数,还支持 Promise
创建组件:
Vue.component(
"example",
() => import("./Component")
)
和高级异步组件(也就是官网提到的):
const AsyncComp = () => ({
// 需要加载的组件。应当是一个 Promise
component: import('./MyComp.vue'),
// 加载中应当渲染的组件
loading: LoadingComp,
// 出错时渲染的组件
error: ErrorComp,
// 渲染加载中组件前的等待时间。默认:200ms。
delay: 200,
// 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
timeout: 3000
})
Vue.component('async-example', AsyncComp)
工厂函数异步组件加载完成
直接跳其他和其它的区别过前面的几个if判断(它们是为高级组件用的),直接到 factory.contexts = [context]
,这个 context
就是V赋值语句的格式ue实例,后面使用了一个 once
函数来定义 resol其他垃圾有哪些东西v构造器的作用e
加载中 和 reject
:
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
一个简单的封装,确保传进来的函数只执行一其他综合收益次构造器,之赋值语句后的 called
就是true了,也就是说确保 r构造器esolve
和 reject
只执行一次,因为多次系统/运维使用异步组件的话,也只能加载的读音 resolve
一次,接着会执行 factory
(就是工厂函数),最后的返回值判断会返回一个 undefined
。
再回到 createCom加载视频插件ponent
方法里,如果 Ctor
是一个 undefin构造器ed
,就其他和其它的区别执行一个 createAsyncPlaceholder
方加载中英文法:
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,也就是一个空的注释节点(就长 <!---->
这个样子插入到dom中,当一个占位符用),然后渲染这个节点,到这里这个异步组件加载完了,系统运维主要做什么然后 resolve
这赋值语句的格式个组件,也就是执行:
function ensureCtor (comp: any, base) {
if (
comp.__esModule ||
(hasSymbol && comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default
}
return isObject(comp)
? base.extend(comp)
: comp
}
// ...
const forceRender = () => {
for (let i = 0, l = contexts.length; i < l; i++) {
contexts[i].$forceUpdate()
}
}
// ...
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()
}
})
这里的 res
就是最上面例子传入的 res
,它其实其他应收款是什么科目就是代码里 expo加载云盘目录失败r加载了恋爱游戏t
出来的那个组件对象,然后加载中执行 ensureCtor
,这个方法做了一个兼容,传入的是一个 module加载失败图片
或者其他,最终都其他应收款可以拿到 export
出来的对象,然后返回了一个异步组件的构造器,把它赋值给 factory.resolved
,然后执行 forceRender
函数,这个函数就是遍历所有的vm实例,每个实例都执行一个赋值表达式 $forceUpdate
,它定义在 src/core/instance/lifecycle.js
:
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
它调用了渲染watcher的 update其他应收款
,然后会走渲染watcher的 getter赋值
,最终就会走vuejs和vue的区别到 mountComponent
方法里的下面这一段:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
也就是说通过 $forceUpdate
,来强制让它渲染一次,这样就会强制又走 render
,触发组件的重新渲染,然后就又会走一遍 createComponent
方法。
之所以这么做是因为 Vue 通常是数据驱动视图重新渲染,但是在整个异步组件加载过程中是没有数据发生变化的,所以通过执行
$forceUpdate
可以强制组件重新渲染一次
然后又会执行到 r其他业务收入esolveAsynComponent
方法里,再来看下上面忽略的前几个if判断:
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
}
它的vue.js前端开发实战 加载了恋爱游戏factory.resolved
其他 判断就成了 true
(赋值表达式因为第一次把 ensur其他货币资金eCtor
返回的构赋值法的原理是什么造器赋值给它),这样就会直接return构造器是否可以被重写出去,接着在 createComponent
里就不会执行 Ctor === undefined
这个逻辑了,接着就和普通的构造器Ctor走一样的逻辑了:安装组件钩子,创加载的读音建组件vnode,patch……
捋一下:由于 Ctor
是一个工厂函数,所以没有 cid
,就会走 resolveAsyncComponent
方法,第一次 factory
参数在前面的if判断里都不满足,所以走到定义 resolve
和 reject
,这里调用了 once系统运维工作内容
方法保证了 resolve
和 其他应付款reject
多次异步也只加载失败会走一次,接着走 factory
(也就是传入的工厂函数),然后return一个 undefined
。接着走 cre系统运维工程师ateComponent
的 c其他和其它的区别reateAsyncPlaceholder
方法,返回一个空vnode(注释节点),然后会走一个patch过程(因为在 createComponent
里)。接着后面回调 resolve
,传入加载中的图片代码里 export
出来的那个组件对象,会走 ensureCto系统/运维r
函数并且赋值给 factory.resolved
,然后通过 forceRender
调用 $forceUpdate
来强制渲染一次,接着会又执行到构造器是什么 res其他业务收入olveAsyncComponent
里赋值运算符的优先级面去,系统运维是干嘛的此赋值语句正确写法时有了 factVue.jsory.resolved
就直接return,后面就安装组件钩子,创建组件vnode,patch……,等其他综合收益patch之后,那个空vnod构造器注入e(注释节点)就被替换掉了。
简化版:第一步创建了空注释节点,第二步渲染挂其他应收款是什么科目载DOM来替换空注释节点。
Promise异步组件
先来看下 Promise
的写法:
Vue.component(
'example',
// 该 `import` 函数返回一个 `Promise` 对象。
() => import('./Component')
)
在执行 resolveAsyncComponent
的时候,和工厂函数类似,不过在走到 res = factory(resovle, reject)vue.js是做什么的
的时候,res其实是例子中 imporvuejs设计与实现t
返回的 promise
对象,所以res就有了 系统/运维then
方法,那就会走到下面的逻辑:
// ...
if (typeof res.then === 'function') {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
}
Promise
方式的话,第一次的 factory.resolved
也是 undefined
,所以就会走 res.then(resolve, reject)
,加载中的图片
然后在异步组件加载成功赋值语句正确写法之后,走这个 resolve
,也就是走上面的定义好的resolve,Vue.js如果加载失败就执行 reject
:
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()
}
})
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()
}
})
后面的逻辑就和工厂函数一样了。
高级异步组件
该写法是在2.3.0+新增的一种写法:
const AsyncComp = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./Component.vue'),
// 加载中应当渲染的组件
loading: {
template:"<div>loading</div>"
},
// 出错时渲染的组件
error: {
template:"<div>error</div>"
},
// 渲染加载中组件前的等待时间,默认:200ms
delay: 200,
// 最长等待时间,超出该时间就渲染错误组件,默认:Infinity
timeout: 3000
})
Vue.component("example", AsyncComp)
这样就定义了一个高级异步组件,猛地一看有种节流的感觉,下面分析一下它的实现。
它依然会走到 resolveAsyncComponent
方法里面,然后第一次执行的时候,最前面的几个if判断:error
、加载完成resolved加载的读音
和 loading
依然是 undefined
,接着定义了 resolve
,reject
等,然后执行 vue.js官网f赋值法ac赋值运算符的优先级tory
,它会执行前面定义 AsyncComp
返回的对象,接着把它赋值给了 res
,然后走到下面:
if (isObject(res)) {
if (typeof res.then === 'function') {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isDef(res.component) && typeof res.component.then === 'function') {
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 {
setTimeout(() => {
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender()
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
setTimeout(() => {
if (isUndef(factory.resolved)) {
reject(
process.env.NODE_ENV !== 'production'
? `timeout (${res.timeout}ms)`
: null
)
}
}, res.timeout)
}
}
}
因为 res
是一个对象,不是一个Promise,所以没有then方法,同时 res
定义了 component
(就是加载的异步组件),并且它有then方法,所以就走 res.component.then
,接着后面做了一系列判断,如果定义了 加载视频插件error
,就创建一个 error
的组件构造器并且扩展到 factory.errorComp
上,如果定义了 load构造器是什么ing
,就创建一个 loading
的组件构造器扩展到 fact其他垃圾有哪些东西or.loadingComp
上,
- 如果有loading,并且
delay
为0(也就是没有延迟时间),就直接把loading
设置为true,这个loading
为true的话,直接影响的就是返回值,返回值就会返加载失败图片回这个fac构造器是否可以被重写tory.loa加载失败dingComp系统运维工程师
,这样的话,返回值就不是undefined
了,那走到createComponent其他货币资金
的时加载失败图片候,就不会走createAsyncPlaceholder
了,加载了恋爱游戏会直接渲染这个load构造器是否可以被重写ingComp
。 - 如果
delay
不是0,就定义一个定时器,注加载视频插件意此时loading
还是没有的(定时器其他综合收益是异步的),这样返回值就还是undefined系统运维是干嘛的
,就会前面一样,渲染一个注释节点,如果后面组件没有加载成功,就会把其他业务收入lo构造器adin其他货币资金g
设置为true,并且会执行forceRender
方法,就又会重新进入resolveAsyncComponent
,此时如加载云盘目录失败果有resolvvue.js官网ed
,就执行resolved
,如果laoding
是构造器是什么true,并且有loadingC加载中omp
,就渲染loadingComp
。
接着判断 timeout
,如果过了这个时间还没有 resolved
的赋值语句话,系统/运维就走 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()
}
})
除了警告信息外,如果定义了 erro加载r加载中Comp
,就把 factory.error
设置为true,然后再调用 构造器是否可以被重写forceRender
,这样再回来的时候,第一个判断赋值语句如何判断正误:
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
errorComp
就会执行(这个error判断放在Vue.js方法的第一行,可见它的优先级是最高的),这样整体过程其实就是先一个空系统运维主要做什么注释节点,再是替换构造器的作用成loading节点,最后替换成真正要展示的组件。
总结
- 异步组件实现的系统运维是干嘛的本质是2次渲染,先渲染成注释节点,当组件加载成功后,再通过 赋值语句如何判断正误
forceRender
重新渲染(通常是2次,上其他应收款是什么科目面的loadin加载中g例子其实是3次)。 - 第二种
Promise构造器注入
的设计是有 webpack
的 import
语法的支持实现的。 - 三种创建异步组件的方式里,高级异步组件是最灵活的,基本上每个状态都会有一个判定,然后通过配置实现了
loading
,resolve
,reject
,ti系统/运维meout
4种状态。
我的公众号:道道里的前端栈,每一天一篇前端文章,加载完成嚼碎的感觉真奇妙~
发表评论