Vue2.0的生命周期钩子一共有10个分别简单介绍如下:
beforeCreate
:在实例初始化之后,数据监听(data observer
) 和 event/watcher
事件配置之前被调用。created
:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据监听(data observer
),属性和方法的运算, watch/event
事件回调。然而,挂载阶段还没开始,$el
属性目前不可见。beforeMount
:在挂载开始之前被调用,相关的 render
函数首次被调用。mounted
:当Vue实例被创建并挂载到DOM元素上时,mounted
立即执行。可以通过this.$el
访问挂载的DOM元素,进行一些依赖于DOM的操作,比如添加事件监听器、获取数据等beforeUpdate
:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated
:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。activated
:keep-alive
组件激活时调用。deactivated
:keep-alive
组件停用时调用。beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。destroyed
:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。在Vue中,请求接口通常放在created
或mounted
生命周期钩子函数中。
created
钩子函数在实例创建之后、挂载之前被调用,此时还没有挂载到DOM上,适合做一些不依赖于DOM的操作,比如数据请求。
mounted
钩子函数在实例挂载到DOM上之后被调用,此时已经完成了DOM的渲染,适合做一些依赖于DOM的操作,比如初始化组件内的某些DOM元素。
根据具体情况,你可以选择在created
或mounted
钩子函数中发起数据请求。
我们创建了一个 app 的Vue根实例,将其挂载到页面 id 为 app 的 Dom 元素上。
然后局部注册了一个组件名为 haohao 并在根实例中将其注册,使其可以在根实例的作用域中使用。将子组件用
包裹,为接下来的测试作准备。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue-LifeClyle</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app" class="jing">
<p>{{message}}</p>
<keep-alive>
<jh-component msg="2017年6月9日" v-if="show"></jh-component>
</keep-alive>
</div>
</body>
<script>
var haohao = {
template: 'from haohao: {{msg}}',
props: ['msg'],
deactivated: function() {
console.log('component deactivated!');
},
activated: function() {
console.log('component activated');
}
};
var app = new Vue({
el: '#app',
data: function() {
return {
message: 'jingjing',
show: true //控制子组件是否显示
};
},
beforeCreate: function() {
console.group('beforeCreate Vue实例创建前的状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(state);
},
created: function() {
console.group('created Vue实例创建完毕后状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(state);
},
beforeMount: function() {
console.group('beforeMount 挂载前状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
},
mounted: function() {
console.group('mounted 挂载后状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
},
beforeUpdate: function() {
console.group('beforeUpdate 更新前状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
console.log('beforeUpdate = ' + document.getElementsByTagName('p')[0].innerHTML);
},
updated: function() {
console.group('updated 更新完成状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
console.log('Updated = ' + document.getElementsByTagName('p')[0].innerHTML);
},
beforeDestroy: function() {
console.group('beforeDestroy 销毁前状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
},
destroyed: function() {
console.group('destroyed 销毁完成状态————————————————————');
var state = {
'el': this.$el,
'data': this.$data,
'message': this.message
}
console.log(this.$el);
console.log(state);
},
components: {
'jh-component': haohao
}
});
</script>
<style>
.jing {
font-size: 50px;
font-weight: bolder;
}
</style>
</html>
beforeCreate
执行时:data
和el
均未初始化,值为undefined
created
执行时:Vue 实例观察的数据对象 data
已经配置好,已经可以得到app.message
的值,但 Vue 实例使用的根 DOM 元素el
还未初始化2、beforeMount
与 mounted
和 activated
与 deactivated
beforeMount
执行时:data
和 el
均已经初始化,但从{{message}}
的展示情况可以看出此时 el
并没有渲染数据,这里就是应用的 Virtual DOM
(虚拟Dom)技术,先把坑占住了。到后面 mounted
挂载的时候再把值渲染上去mounted
执行时:此时 el
已经渲染完成并挂载到实例上component activated
被打印出来了,说明子组件jh-component
被
包裹,随 el
的挂载而触发了。然后我们进行一些操作,在控制台输入 app.show = false
我们再来看看有什么变化,测试结果如下图:
data
的值,所以会触发beforeUpdate
和updated
钩子函数,我们看到deactivated
钩子已经触发,表示
已经停用。3、beforeUpdate
和 updated
beforeUpdate
和updated
触发时,el
中的数据都已经渲染完成,但根据控制台打印的信息可知,只有当updated
钩子被调用时候,组件dom才会被更新。
这个阶段做的第一件事,就是用 new
创建一个 Vue 实例对象
new Vue({
el:'#app',
store,
router,
render: h => h(App)
})
能用 new 那肯定是有一个构造函数的,我们来看一下
源码地址:src/core/instance/index.js - 8行
function Vue (options) {
if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
_init()
源码地址:src/core/instance/init.js - 15行
这里去掉了一些环境判断的,主要流程就是
、
、
、directive
、filter
、本文最开始的钩子函数名称列表等合并到 Vue.options
上面beforeCreate
和 created
$mount 挂载
进入下一阶段export function initMixin (Vue: Class<Component>) {
// 在原型上添加 _init 方法
Vue.prototype._init = function (options?: Object) {
// 保存当前实例
const vm: Component = this
// 合并配置
if (options && options._isComponent) {
// 把子组件依赖父组件的 props、listeners 挂载到 options 上,并指定组件的$options
initInternalComponent(vm, options)
} else {
// 把我们传进来的 options 和当前构造函数和父级的 options 进行合并,并挂载到原型上
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher...等
initEvents(vm) // 初始化事件:$on, $off, $emit, $once
initRender(vm) // 初始化渲染: render, mixin
callHook(vm, 'beforeCreate') // 调用生命周期钩子函数
initInjections(vm) // 初始化 inject
initState(vm) // 初始化组件数据:props, data, methods, watch, computed
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // 调用生命周期钩子函数
if (vm.$options.el) {
// 如果传了 el 就会调用 $mount 进入模板编译和挂载阶段
// 如果没有传就需要手动执行 $mount 才会进入下一阶段
vm.$mount(vm.$options.el)
}
}
}
$mount()
源码地址:dist/vue.js - 11927行
Vue.prototype.$mount = function ( el, hydrating ) {
el = el && query(el);
var options = this.$options;
// 如果没有 render
if (!options.render) {
var template = options.template;
// 再判断,如果有 template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
return this
}
// 再判断,如果有 el
} else if (el) {
template = getOuterHTML(el);
}
}
return mount.call(this, el, hydrating)
};
$mount
主要就是判断要不要编译,使用哪一个模板编译,需要注意的就是判断顺序了,我们来看一下这段代码
<div id='app'>
<p>{{ name }}</p>
</div>
<script>
new Vue({
el:'#app',
data:{ name:'123' },
template:'掘金',
render(h){
return h('div', {}, '好好学习,天天向上')
}
})
</script>
结合源码马上就知道会渲染出来的只有
因为源码中是优先判断 render
是否存在,如果存在,就直接使用 render
函数了
如果没有,再判断 template
和 el
,如果有 template
,就不会管 el
了
所以优先级顺序是:render > template > el
,因为不管是 el
挂载的,还是 template
最后都会被编译成 render
函数
render
返回的虚拟 DOM
创建真实的 DOM 节点,插入到视图中,完成渲染这里主要做的事就是
beforeMount
_update()
方法对新老虚拟 DOM 进行 patch
以及 new Watcher
对模板数据做响应式处理mounted
源码地址:src/core/instance/lifecycle.js - 141行
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 判断有没有渲染函数 render
if (!vm.$options.render) {
// 如果没有,默认就创建一个注释节点
vm.$options.render = createEmptyVNode
}
// 调用生命周期钩子函数
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
// 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Diff )到真实DOM,这里是首次渲染
vm._update(vm._render(), hydrating)
}
// 为当前组件实例设置观察者,监控 updateComponent 函数得到的数据,下面有介绍
new Watcher(vm, updateComponent, noop, {
// 当触发更新的时候,会在更新之前调用
before () {
// 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行
if (vm._isMounted && !vm._isDestroyed) {
// 调用生命周期钩子函数
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 没有老的 vnode,说明是首次渲染
if (vm.$vnode == null) {
vm._isMounted = true
// 调用生命周期钩子函数
callHook(vm, 'mounted')
}
return vm
}
关于响应式原理
有关 Diff 算法源码的完整流程剖析
源码地址:src/core/instance/lifecycle.js - 59行
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el // 当前组件根节点
const prevVnode = vm._vnode // 老的 vnode
vm._vnode = vnode // 更新老的 vnode
// 如果是首次渲染
if (!prevVnode) {
// 对 vnode 进行 patch 创建真实的 DOM,挂载到 vm.$el 上
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 修改的时候,进行新老 vnode 对比,并回修改后的真实 DOM
vm.$el = vm.__patch__(prevVnode, vnode)
}
// 删除老根节点的引用
if (prevEl) prevEl.__vue__ = null
// 更新当前根节点的引用
if (vm.$el) vm.$el.__vue__ = vm
// 更新父级的引用
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
beforeDestory
destoryed
源码地址:src/core/instance/lifecycle.js - 97行
Vue.prototype.$destroy = function () {
const vm: Component = this
// 如果实例正在被销毁的过程中,直接跳过
if (vm._isBeingDestroyed) {
return
}
// 调用生命周期钩子函数
callHook(vm, 'beforeDestroy')
// 更新销毁过程状态
vm._isBeingDestroyed = true
// 获取父级
const parent = vm.$parent
// 如果父级存在,并且父级没有在被销毁,并且不是抽象组件而是真实组件(就是抽象组件,它的abstract就为true)
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
// 从父级中删除当前组件
remove(parent.$children, vm)
}
// 移除实例的所有观察者
if (vm._watcher) {
// 删除实例自身依赖
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
// 删除实例内数据对其他数据的依赖
vm._watchers[i].teardown()
}
// 删除数据对象的引用
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// 更新组件销毁状态
vm._isDestroyed = true
// 删除实例的虚拟 DOM
vm.__patch__(vm._vnode, null)
// 调用生命周期钩子函数
callHook(vm, 'destroyed')
// 关闭所有事件监听
vm.$off()
// 删除当前根组件的引用
if (vm.$el) {
vm.$el.__vue__ = null
}
// 删除父级的引用
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
在 patch
源码里看到有三次调用,源码地址:src/core/vdom/patch.js
新vnode
不存在,老vnode
存在的时候,就触发卸载老vnode
对应组件的 destroy
。702行新vnode
根节点被修改的时候,调用老vnode
对应组件的 destroy
。767行老vnode
对应组件的 destroy
。实例销毁后,Vue实例指示的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
setup
函数是组合式API的入口函数,默认导出配置选项,setup
函数声明,返回模板需要数据与函数。
setup
函数是 Vue3
特有的选项,作为组合式API的起点beforeCreate
之前执行this
不是组件实例,是 undefined
setup
返回import { reactive, computed } from 'vue';
export default {
setup() {
// 定义响应式数据
const count = reactive(0);
// 定义计算属性
const double = computed(() => count * 2);
// 定义方法
const increment = () => {
count++;
};
// 返回响应式数据和方法,以便在模板中使用
return {
count,
increment,
double
};
}
}
在 setup
中,我们可以使用 Vue3
提供的多个工具函数来定义响应式数据、监听生命周期钩子、处理计算属性、声明事件处理函数等。这些函数包括:
reactive
:用于创建响应式对象ref
:用于创建一个单一的响应式值computed
:用于创建计算属性watch
:用于监听响应式数据的变化onMounted
、onUpdated
和 onUnmounted
:用于监听生命周期钩子toRefs
:用于将响应式对象转换为普通对象inject
和 provide
:用于跨层级组件传递数据getCurrentInstance
:用于访问当前组件实例onBeforeMount
钩子函数会在组件挂载到 DOM 前运行,可以用来在组件挂载前执行一些初始化操作。
<script setup>
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
console.log('Before mount')
})
</script>
onMounted
钩子函数会在组件挂载到 DOM
后运行,通常用于获取数据和初始化页面状态等操作。
<template>
<div>{{ message }}</div>
</template>
<script setup>
import { onMounted, reactive } from 'vue'
const state = reactive({
message: ''
})
onMounted(() => {
// 发送 AJAX 请求,获取数据
fetch('/api/data')
.then(res => res.json())
.then(data => {
state.message = data.message
})
})
</script>
在上面的例子中,我们通过 onMounted
钩子在组件挂载后发送 AJAX 请求,获取数据并更新组件状态中的 message
字段。
需要注意的是,在 Vue3
中,onMounted
和 onBeforeMount
钩子需要在 setup
函数中使用。
onBeforeUpdate
钩子函数会在数据重新渲染之前运行,可以用来在组件更新前执行一些操作。
<script setup>
import { onBeforeUpdate } from 'vue'
let count = 1
onBeforeUpdate(() => {
console.log('Before update', count)
})
const handleClick = () => {
count++
}
</script>
<template>
<div>
<p>{{ count }}</p>
<button @click="handleClick">增加</button>
</div>
</template>
onUpdated
钩子函数会在数据重新渲染后运行,通常用于更新 DOM、执行动画或获取最新的状态等操作。
<template>
<div>
<p>{{ message }}</p>
<button @click="handleClick">获取最新的消息</button>
</div>
</template>
<script setup>
import { onMounted, onUpdated, ref } from 'vue'
const message = ref('Hello, world!')
onMounted(() => {
// 模拟异步获取消息
setTimeout(() => {
message.value = 'Hello, Vue 3!'
}, 2000)
})
onUpdated(() => {
console.log('DOM updated')
})
const handleClick = () => {
alert(message.value)
}
</script>
onBeforeUnmount
钩子函数会在组件卸载之前运行,可以用来清除定时器、取消事件监听器等操作。
<script setup>
import { onBeforeUnmount, ref } from 'vue'
const timer = ref(null)
onBeforeUnmount(() => {
clearInterval(timer.value)
})
const startTimer = () => {
timer.value = setInterval(() => {
console.log('Hello, world!')
}, 1000)
}
const stopTimer = () => {
clearInterval(timer.value)
}
</script>
<template>
<div>
<p>定时器示例</p>
<button @click="startTimer">开始</button>
<button @click="stopTimer">停止</button>
</div>
</template>
通过 onBeforeUnmount
钩子注册了一个函数,在组件卸载之前清除定时器。同时,在方法中添加了两个按钮事件,用于启动和停止计时器。
onUnmounted
钩子函数会在组件卸载后运行,通常用于清理一些资源或取消订阅。
<template>
<div>
<p>{{ message }}</p>
<button @click="unsubscribe">取消订阅</button>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const message = ref('')
let subscription = null
onMounted(() => {
// 模拟创建一个订阅
subscription = setInterval(() => {
message.value = new Date().toLocaleTimeString()
}, 1000)
})
onUnmounted(() => {
// 在组件卸载后取消订阅
clearInterval(subscription)
})
const unsubscribe = () => {
clearInterval(subscription)
}
</script>