# Vue2.x 的生命周期
前言
vue 中不同的时机执行不同的生命周期:
- 1.首次加载依次执行
- 2.数据更新依次执行
- 3.数据销毁依次执行
- 4.组件报错时执行
先看一个问题引发的思考:
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 beforeUpdate updated nextTick1 nextTick2
nextTick1 、 nextTick2 为微任务异步执行,最后打印的,msg 修改引起的更新,所以 beforeUpdate、updated 生命周期同步执行?
目录
# 1.首次加载
# 1.1 beforeCreate
- Vue
function Vue(options) {
this._init(options)
}
2
3
- 初始化相关逻辑
Vue.prototype._init = function (options?: Record<string, any>) {
...
callHook(vm, "beforeCreate", undefined, false /* setContext */)
initInjections(vm)
initState(vm)
initProvide(vm)
}
2
3
4
5
6
7
- initInjections 对 inject 进行初始化
- initState 对 methods、data、computed、watch 进行初始化
结论
由此可知 beforeCreate
不能访问 methods、data、computed、watch 数据
使用场景
一般插件会在此生命周期混入一些逻辑处理,如:vuex 、vue-router
# 1.2 created
Vue.prototype._init = function (options?: Record<string, any>) {
...
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, "created")
}
2
3
4
5
6
7
结论
由此可知 created
能访问 methods、data、computed、watch 数据
使用场景
可以用来处理接口请求相关的逻辑
# 1.3 beforeMount
Vue.prototype._init = function (options?: Record<string, any>) {
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
2
3
4
5
6
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
2
3
4
5
6
7
- 重写$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
return mount.call(this, el, hydrating)
}
2
3
4
5
6
7
8
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
...
callHook(vm, "beforeMount")
}
2
3
4
5
6
7
8
结论
由此可知没有 el 属性,没有 vm.$mount
,beforeMount、mounted 不会调用,手动挂载 vm.$mount("#app")后可以恢复正常
new Vue({
beforeCreate: function () {
console.log("调用了beforeCreate")
},
created: function () {
console.log("调用了created")
},
beforeMount: function () {
console.log("调用了beforeMount")
},
mounted: function () {
console.log("调用了mounted")
},
})
// 输出结果
// 调用了beforeCreate
// 调用了created
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用场景
可以处理 dom 挂载前业务逻辑
# 1.4 mounted
- 渲染 watcher
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
...
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
const watcherOptions: WatcherOptions = {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate")
}
},
}
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, "mounted")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- Watcher
export default class Watcher implements DepTarget {
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
this.getter = expOrFn
this.value = this.lazy ? undefined : this.get()
}
get() {
value = this.getter.call(vm, vm)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
根组件直接会走 mounted 生命周期
- 组件生命周期 init
const componentVNodeHooks = {
insert(vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, "mounted")
}
},
}
2
3
4
5
6
7
8
9
非根组件走组件自身的 hooks 触发 mounted:render-->组件 vnode--> patch-->创建组件-->实例化组件(内部调用 extend 相当于 new Vue)-- invokeInsertHook --> queue[i].data.hook.insert(queue[i])
结论
由此可知根组件和非根组件执行 mounted 的方式是不一样的,异步组件走非根组件逻辑,beforeCreate created beforeMount mounted 异步执行,其他情况下 beforeCreate created beforeMount mounted 同步执行
使用场景
可以处理 dom 相关的业务逻辑
# 1.5 父子组件单独加载案例
初始化
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeCreate parent-created parent-beforeMount parent-mounted 创建子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated
# 1.6 父子组件一起加载案例
初始化
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 步骤 8 步骤 9 父组件 parent-beforeCreate parent-created parent-beforeMount parent-mounted 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated
# 2.数据更新
# 2.1 beforeUpdate
- 渲染 watcher
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
...
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
const watcherOptions: WatcherOptions = {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate")
}
},
}
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- watcher 类,响应式数据第一次访问时进行依赖收集,会将渲染 watcher 存储起来,数据有修改时派发更新
export default class Watcher implements DepTarget {
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
if (options) {
this.before = options.before
}
}
addDep(dep: Dep) {
...
dep.addSub(this)
}
update() {
...
queueWatcher(this)
}
run() {
...
this.cb.call(this.vm, value, oldValue) // cb 就是 updateComponent
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 响应式数据
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
...
dep.depend() // 依赖收集
},
set: function reactiveSetter(newVal) {
...
dep.notify() // 派发更新
},
})
2
3
4
5
6
7
8
9
10
- Dep
export default class Dep {
addSub(sub: DepTarget) {
this.subs.push(sub)
}
depend(info?: DebuggerEventExtraInfo) {
...
Dep.target.addDep(this) //Dep.target 就是 Watcher
}
notify(info?: DebuggerEventExtraInfo) {
...
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
sub.update()
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- queueWatcher
let waiting = false
export function queueWatcher(watcher: Watcher) {
...
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue) // 异步任务 nextTick
}
}
2
3
4
5
6
7
8
- flushSchedulerQueue
function resetSchedulerState() {
waiting = flushing = false
}
function flushSchedulerQueue() {
flushing = true
let watcher, id
queue.sort(sortCompareFn)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.before() // 生命周期函数 beforeUpdate
watcher.run() // 更新模板
}
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
cleanupDeps()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结论
由此可知 beforeUpdate 通过 nextTick 异步执行
使用场景
可以处理 dom 更新前 相关的业务逻辑
# 2.2 updated
- callUpdatedHooks
function callUpdatedHooks(queue: Watcher[]) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
callHook(vm, "updated") // 生命周期函数
}
}
2
3
4
5
6
7
8
结论
由此可知 Vue 的 updated 钩子是异步的。Vue 在 DOM 更新完成后,会通过异步队列来执行 updated 钩子函数,但不保证它会在 DOM 更新完成后的下一个事件循环中同步触发。这意味着,如果你在 updated 钩子中需要访问更新后的 DOM 元素,你应该使用 this.$nextTick 来确保你的代码在 DOM 更新完成后执行
- 一般不要操作数据 可能会导致死循环
使用场景
# 2.3 父子组件单独加载案例
创建子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 步骤 6 步骤 7 步骤 8 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeCreate child-created child-beforeMount child-mounted child-activated
# 2.4 父子组件一起加载案例
更新子组件数据
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeUpdate child-updated
# 3.数据销毁
# 3.1 beforeDestroy
组件中使用 this.$destroy() 手动卸载组件
Vue.prototype.$destroy = function () {
...
callHook(vm, 'beforeDestroy')
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
vm.__patch__(vm._vnode, null)
}
2
3
4
5
6
7
8
9
结论
由此可知 beforeDestroy 同步执行,在 beforeDestroy 生命钩子调用之前,所有实例都可以用
使用场景
# 3.2 destroyed
Vue.prototype.$destroy = function () {
...
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
vm.__patch__(vm._vnode, null)
callHook(vm, 'destroyed')
}
2
3
4
5
6
7
8
9
结论
由此可知 destroyed 同步执行,在 destroyed 生命钩子调用之前,实列上的方法 监听都被移除掉,所有的子实例也会被销毁
使用场景
卸载定时器或者原生 js 相关方法
# 3.3 有 keep-alive 包裹案例
销毁子组件
组件类型 步骤 1 步骤 2 步骤 3 父组件 parent-beforeUpdate parent-updated 子组件 child-deactivated
# 3.4 无 keep-alive 包裹案例
这里使用 v-if 来销毁子组件
销毁子组件
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 parent-beforeUpdate parent-updated 子组件 child-beforeDestroy child-destroyed
# 4.其他生命钩子
# 4.1 activated
const componentVNodeHooks = {
insert(vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, "mounted")
}
if (vnode.data.keepAlive) {
activateChildComponent(componentInstance, true /* direct */)
}
},
}
2
3
4
5
6
7
8
9
10
11
12
export function activateChildComponent(vm: Component, direct?: boolean) {
...
callHook(vm, "activated")
}
2
3
4
结论
由于在组件中使用 keep-alive,所以被包裹的组件不可能是根组件,组件如果执行 mounted,会在后面执行 activated
使用场景
缓存组件
# 4.2 deactivated
const componentVNodeHooks = {
destroy(vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */)
}
}
},
}
2
3
4
5
6
7
8
9
10
11
12
export function deactivateChildComponent(vm: Component, direct?: boolean) {
...
callHook(vm, "deactivated")
}
2
3
4
结论
由于在组件中使用 keep-alive,所以被包裹的组件不可能是根组件,组件 destroy 和 deactivated 是互斥的,既只能执行其中一个
使用场景
缓存组件
# 4.3 errorCaptured
export function handleError(err: Error, vm: any, info: string) {
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false
} catch (e: any) {
}
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
结论
当捕获一个来自子孙组件的错误时被调用
使用场景
捕获 vue 内部错误
# 5.案例分析
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 beforeUpdate updated nextTick1 nextTick2
通过上述知识点的分析,mounted 由于是非根组件,不确定是否是同步执行,beforeUpdate、updated 异步执行
这里有两点要研究:1.此处的 mounted 是否是同步加载、2.beforeUpdate、updated 异步执行过程是否和 this.$nextTick 一致
# 5.1 mounted 同步异步
建立一个空项目,将上述代码放入App.vue
中运行查看
结果
组件类型 步骤 1 步骤 2 步骤 3 步骤 4 父组件 nextTick1 beforeUpdate updated nextTick2
这里可以确定组件是同步加载,符合预期
猜想案例中 mounted 是异步加载,所以组件是异步的,通过 () => import('./xxx.vue')
的方式异步加载组件,发现和案例中的结果一致,所以可以得出结论
这里的组件异步加载的,mounted 是异步执行的
我们都知道实例化渲染watcher
时/更新数据时,会触发传入的updateComponent
函数,通过vm._render
生成vnode
,让后通过vm._update
将vnode
派发更新到dom
上
具体分析 render
和update
过程,可以了解其大概的流程
# 5.1.1 render 生成 vnode
- createComponent
export function createComponent(
Ctor: typeof Component | Function | ComponentOptions | void,
data: VNodeData | undefined,
context: Component,
children?: Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
}
}
const name = getComponentName(Ctor.options) || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- resolveAsyncComponent
export function resolveAsyncComponent(
factory: { (...args: any[]): any, [keye: string]: any },
baseCtor: typeof Component
): typeof Component | void {
const owner = currentRenderingInstance
const owners = (factory.owners = [owner])
const forceRender = (renderCompleted: boolean) => {
for (let i = 0, l = owners.length; i < l; i++) {
owners[i].$forceUpdate()
}
}
const resolve = once((res: Object | Component) => {
factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {
forceRender(true)
} else {
owners.length = 0
}
})
const res = factory(resolve, reject)
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
}
return factory.loading ? factory.loadingComp : factory.resolved
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
执行res.then
,通过forceRender
强制更新组件
- createAsyncPlaceholder
export function createAsyncPlaceholder(
factory: Function,
data: VNodeData | undefined,
context: Component,
children: Array<VNode> | undefined,
tag?: string
): VNode {
const node = createEmptyVNode()
node.asyncFactory = factory
node.asyncMeta = { data, context, children, tag }
return node
}
2
3
4
5
6
7
8
9
10
11
12
- $forceUpdate
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
2
3
4
5
6
通过渲染watcher
上的update
方法更新组件
- watcher
export default class Watcher implements DepTarget {
update() {
...
queueWatcher(this)
}
}
2
3
4
5
6
- queueWatcher
let waiting = false
export function queueWatcher(watcher: Watcher) {
...
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue) // 异步任务 nextTick
}
}
2
3
4
5
6
7
8
- flushSchedulerQueue
function resetSchedulerState() {
waiting = flushing = false
}
function flushSchedulerQueue() {
flushing = true
let watcher, id
queue.sort(sortCompareFn)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.before() // 生命周期函数 beforeUpdate
watcher.run() // 更新模板
}
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到异步任务通过nextTick
异步队列就开始执行了
# 5.1.2 patch 到 dom
- patch
return function patch(oldVnode, vnode, hydrating, removeOnly) {
...
createElm(vnode, insertedVnodeQueue)
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
}
2
3
4
5
invokeInsertHook 调用 mounted
- createElm
function createElm() {
...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
}
2
3
4
5
6
- createComponent
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- componentVNodeHooks
const componentVNodeHooks = {
init(vnode: VNodeWithData, hydrating: boolean): boolean | void {
...
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
))
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
},
}
2
3
4
5
6
7
8
9
10
createComponentInstanceForVnode 相当于 new Vue
child.$mount 手动挂载由于不是根组件,所以不会走 mounted,只走 beforeMount
- createComponentInstanceForVnode
export function createComponentInstanceForVnode(): Component {
...
return new vnode.componentOptions.Ctor(options)
}
2
3
4
- invokeInsertHook
function invokeInsertHook(vnode, queue, initial) {
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
2
3
4
5
6
7
8
9
- insert
const componentVNodeHooks = {
insert(vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, "mounted")
}
},
}
2
3
4
5
6
7
8
9
到此为止,父组件的挂载结束,同步代码执行完毕
上述render
阶段产生的异步任务开始执行
# 5.1.3 $forceUpdate
从上述流程中可以看到,通过$forceUpdate
可以更新组件,并且时放到$nextTick
中进行异步更新的,此时触发更新的主体还是在父组件中,放入任务队列中的渲染watcher
还是父组件
更新组件走更新逻辑,所以父组件上会触发beforeUpdate
和updated
两个生命周期(可以自己建一个空项目验证,父组件下有一个异步组件,父组件未更新数据却会触发更新的生命周期)
- 父组件更新时:render 阶段
createComponent
之前初始化时由于是异步组件,走异步组件加载逻辑resolveAsyncComponent
,现在可以拿到数据了走Ctor = baseCtor.extend(Ctor as typeof Component)
逻辑
Vue.extend = function (extendOptions: any): typeof Component {
...
const Super = this
const Sub = function VueComponent(this: any, options: any) {
this._init(options)
} as unknown as typeof Component
return Sub
}
2
3
4
5
6
7
8
可以看到子组件通过extend
继承到了_init
,后续在有需要的地方通过new
就可以走和new Vue
一样的初始化逻辑
通过createComponent
生成组件vnode
后就进入了 patch 阶段
patch
vm._update
拿到vnode
后,会在createElm
通过createComponent
执行组件上的生命周期init
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
))
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
2
3
4
5
从上面可以知道这两步两个目的:
- 1.实例化子组件:
createComponentInstanceForVnode
执行后会执行this._init
- 2.挂载子组件:会生成渲染
watcher
到此处已经进入了子组件内部逻辑处理部分了,后面的流程可以看作,一个同步组件的初次加载流程了,由于不是根组件所以步骤2
这里只执行beforeMount
,不执行mounted
mounted
的执行在组件的生命周期insert
中执行
insert(vnode: MountedComponentVNode) {
...
callHook(componentInstance, 'mounted')
}
2
3
4
此时可以知道异步子组件和同步子组件不同之处有:(父组件为根组件、或同步组件)
- 1.父组件没有更新操作,使用同步子组件父组件不会触发
beforeCreate
、created
这两个生命周期,但是用异步子组件时,父组件会触发这两个生命周期 - 2.同步子组件在父组件中初始化时是同步渲染的,派发过程也是同步的;异步子组件在父组件中初始化时是异步渲染的,派发过程也是异步的,生命周期
beforeCreate
、created
、beforeMount
、mounted
都是异步执行的,且异步都是在nextTick
中执行的
都按微任务理解,更新也是微任务,那么打印顺序应该是:nextTick1
nextTick2
beforeUpdate
updated
但是真实结果和这个不一样,那么具体来分析下nextTick
# 5.2 $nextTick
- nextTick
const callbacks: Array<Function> = [] // 关键点
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e: any) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
分析可以知道 nextTick
原理就是开启一个队列,存储$nextTick
的回调函数,同时开启微任务,与一般的微任务的区别就是这里的任务是放入一个队列中,同一段代码同步执行都会放入队列中
mounted() {
this.$nextTick(() => {
logsData.push("nextTick1")
})
this.msg = "111"
this.$nextTick(() => {
logsData.push("nextTick2")
})
},
beforeUpdate() {
logsData.push("beforeUpdate")
},
updated() {
logsData.push("updated")
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
异步组件挂载过程中是在微任务中执行mounted
,任务都放在updatedQueue
中,这个任务还没执行完,此时this.$nextTick
开启了另一个微任务,任务不会放到updatedQueue
中;this.msg = "111"
触发更新操作,更新任务是放到updatedQueue
中的,所以在这个微任务中会继续执行beforeUpdate
、updated
,然后才执行
nextTick1
、nextTick2
总结
通过上述案例可以了解到:
- 1.同一组件内生命周期首次加载同步执行、数据更新异步加载、数据销毁同步加载
- 2.异步组件会影响父组件的生命周期,异步组件内部自身执行逻辑顺序和同步时也是不一样的,特别是使用自带的
nextTick
时容易出现 bug
← Vue2.x 的指令 Vue2.x 的动画 →