Skip to content

Vue Lifecycle

Vue 实例挂载过程

  1. 初始化实例:传入 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
  1. 注入数据处理 inject,然后才是 state (props/method/data/computed/watch),最后是 provide, 使用代理模式把各个实例挂载到实例上。
    1. 所以 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]
	  }
	})
  1. 挂载阶段:定义一个更新函数 updateComponent,基于该函数新建 watcher 实例
    1. updateComponent会调用 render 生成 VDOM, 然后再交给 update 函数进行更新
    2. 有了 watcher 之后,就会在执行回调函数期间进行依赖收集,当依赖变化时重新运行回调函数,同样地,生成VDOM,然后 update
    3. update 中间就执行了 patch 函数,比对新旧 DOM ,异步最小化更新真实 DOM
    4. 如果遇到子组件,则再对子组件进行 实例化-->数据注入-->挂载
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-onMounted

Vue 重渲染过程

  1. Watcher 监听依赖变化 当数据发生变化时,所有依赖该数据的 watcher 都会重新运行,比如上文提到的用updateComponent创建的watcher
  2. nextTick 异步更新 Watcher 会被 Scheduler放入异步的为队列中执行,避免多个依赖的数据发生变化时导致的频繁渲染。
  3. updateComponent 执行 调用内部的 render --> update --> patch , 涉及到子组件的创建、删除和更新,也会触发子组件的一系列生命周期。

当有一个父组件A,子组件B时,在初始化挂载时,他们的生命周期触发顺序应该是

txt
A-beforeUpdate
	B-beforeUpdate
	B-updated
A-updated

Vue 销毁过程

与重渲染过程类似,Vue2 中是 beforeDestroydestroy, Vue3 中是 beforeUnmount/onBeforeUnmount和`unmounted/onUnmounted

txt
A-beforeUnmount
	B-beforeUnmount
	B-unmounted
A-unmounted

其他生命周期钩子

onActivated / onDeactivate

这两个钩子是用于处理被 <keep-alive>缓存的组件,当组件被激活和失活的时候被调用。类似于mountedunMounted ,但内部组件是被缓存了的。

下面的例子采用动态组件 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