Vue Lifecycle
Vue 实例挂载过程
- 初始化实例:传入 options 到初始化函数,初始化事件、生命周期、渲染函数
js
initMixin(Vue); // 定义 _init
stateMixin(Vue); // 定义 $set $get $delete $watch 等
eventsMixin(Vue); // 定义事件 $on $once $off $emit
lifecycleMixin(Vue);// 定义 _update $forceUpdate $destroy
renderMixin(Vue); // 定义 _render- 注入数据:先处理 inject,然后才是 state (props/method/data/computed/watch),最后是 provide, 使用代理模式把各个实例挂载到实例上。
- 所以 inject 不能异步进行,这破坏了依赖
js
callHook('beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook('created')js
// 代理模式,就是把所有 state 都挂载在实例上
const data = options.data()
observer(data)
Object.defineProperty(this, 'a', {
get(){
return data.a
}
})
// vue3 就用 proxy
const instance = {
setupState: {}, // setup 返回的对象
props: {}, // props
ctx: {}, // 上下文
data: {}
}
const proxy = new Proxy(instance, {
get(target, key) {
// 优先从 setupState 找,再找 data、props...
if (key in target.setupState) return target.setupState[key]
if (key in target.props) return target.props[key]
}
})- 挂载阶段:定义一个更新函数
updateComponent,基于该函数新建 watcher 实例updateComponent会调用 render 生成 VDOM, 然后再交给update函数进行更新- 有了
watcher之后,就会在执行回调函数期间进行依赖收集,当依赖变化时重新运行回调函数,同样地,生成VDOM,然后update update中间就执行了patch函数,比对新旧 DOM ,异步最小化更新真实 DOM- 如果遇到子组件,则再对子组件进行 实例化-->数据注入-->挂载
js
// vue2 中
callHook('beforeMount')
const updateComponent = ()=>{
vm._update(vm._render())
}
new Watcher(vm, updateComponent)
callHook(vm, 'mounted')综上,这里我们可以推导得到,当有一个父组件A,子组件B时,在初始化挂载时,他们的生命周期触发顺序应该是
txt
A-beforeCreated
A-created
A-beforeMount
B-beforeCreated
B-created
B-beforeMounted
B-mounted
A-mounted在 Vue3 的 Composition API 中,beforeCreated 和 created 被 setup 替代了。如果在 setup 执行间加一个 log('setup-run'),经过测试,A为父,B为子触发顺序如下,可以看到,整体的生命周期和选项式 API 中的执行顺序一致。
txt
A-setup-run
A-onBeforeMount
B-setup-run
B-onBeforeMount
B-onMounted
A-onMountedVue 重渲染过程
- Watcher 监听依赖变化 当数据发生变化时,所有依赖该数据的
watcher都会重新运行,比如上文提到的用updateComponent创建的watcher nextTick异步更新 Watcher 会被Scheduler放入异步的为队列中执行,避免多个依赖的数据发生变化时导致的频繁渲染。updateComponent执行 调用内部的 render --> update --> patch , 涉及到子组件的创建、删除和更新,也会触发子组件的一系列生命周期。
当有一个父组件A,子组件B时,在初始化挂载时,他们的生命周期触发顺序应该是
txt
A-beforeUpdate
B-beforeUpdate
B-updated
A-updatedVue 销毁过程
与重渲染过程类似,Vue2 中是 beforeDestroy 和 destroy, Vue3 中是 beforeUnmount/onBeforeUnmount和`unmounted/onUnmounted
txt
A-beforeUnmount
B-beforeUnmount
B-unmounted
A-unmounted其他生命周期钩子
onActivated / onDeactivate
这两个钩子是用于处理被 <keep-alive>缓存的组件,当组件被激活和失活的时候被调用。类似于mounted 和 unMounted ,但内部组件是被缓存了的。
下面的例子采用动态组件 component + :is 实现了一个 tab 页切换效果,切换 tab 会触发子组件的 onActivated onDeactivated
vue
<template>
<div>
<div class="tab-nav">
<button
v-for="tab, i in tabs"
:id="tab.id"
@click="jump2Tab(i)"
:class="{active: i === activeTabId}"
>{{ tab.label }}</button>
</div>
<div class="tab-content">
<keep-alive>
<component :msg="activeTab.msg" :is="activeTab.comp"></component>
</keep-alive>
</div>
</div>
</template>
<script setup>
import Child from './Child.vue'
import Content from './Content.vue'
import { computed, ref } from 'vue'
/////// Tabs //////////////////////////////////
const tabs = [
{ id: 1, label: 'Tab 1', msg: 'content1', comp: Child },
{ id: 2, label: 'Tab 2', msg: 'content2', comp: Content },
{ id: 3, label: 'Tab 3', msg: 'content3', comp: Content }
]
const activeTabId = ref(0)
const activeTab = computed(() => {
return tabs.at(activeTabId.value)
})
const jump2Tab = (id) => activeTabId.value = id
</script>onRenderTracked / onRenderTriggered
在开发阶段 debug 用 onRenderTracked 当组件渲染过程中追踪到响应式依赖时调用 callback onRenderTriggered 当响应式依赖的变更触发了组件渲染时调用 callback
vue
<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue';
const renderCount = ref(0)
onRenderTracked((e) => {
console.log('tracked! ', e)
})
onRenderTriggered((e) => {
console.log('triggered! ', e)
})
</script>
<template>
<span v-show="false">{{ renderCount }}</span>
<button @click="renderCount++">reRender</button>
</template>上面的代码通过一个不可见的 dom 来控制页面的 rerender, 其中 renderCount 表示页面的渲染次数
txt
// 首次渲染时
tracked! , e(target.value = 0) ==> 跟踪值为 0 的响应式依赖
// 点击reRender时
triggered! , e(target.newValue = 1) ==> 因为变量新值为 1,所以触发渲染
tracked! , e(target.value = 0) ==> 跟踪值为 1 的响应式依赖onServerPrefetch
注册一个异步函数,在组件实例在服务器上被渲染之前调用。 SSR only