// src/core/instance/lifecycle.js
// callhook 函数的功能就是在当前vue组件实例中,调用某个生命周期钩子注册的所有回调函数。
// vm:Vue实例
// hook:生命周期名字
export function callHook (vm: Component, hook: string) {
pushTarget()
const handlers = vm.$options[hook]
// 初始化合并 options 的过程 、,将各个生命周期函数合并到 options 里
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
// src/core/util/error.js
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
我们从上面的代码中可以看到
callHook
中调用了invokeWithErrorHandling
方法,在invokeWithErrorHandling
方法中,使用了apply
和call
改变了this
指向,而在箭头函数中this
指向是无法改变的,所以我们在编写生命周期函数的时候不能使用箭头函数。
beforeCreate 和 created
// src/core/instance/init
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
// 合并选项部分已省略
initLifecycle(vm)
// 主要就是给vm对象添加了 $parent、$root、$children 属性,以及一些其它的生命周期相关的标识
initEvents(vm) // 初始化事件相关的属性
initRender(vm) // vm 添加了一些虚拟 dom、slot 等相关的属性和方法
callHook(vm, 'beforeCreate') // 调用 beforeCreate 钩子
//下面 initInjections(vm) 和 initProvide(vm) 两个配套使用,用于将父组件 _provided 中定义的值,通过 inject 注入到子组件,且这些属性不会被观察
initInjections(vm)
initState(vm) // props、methods、data、watch、computed等数据初始化
initProvide(vm)
callHook(vm, 'created') // 调用 created 钩子
}
}
// src/core/instance/state
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
- 我们可以看到
beforeCreate
钩子调用是在initState
之前的,而从上面的第二段代码我们可以看出initState
的作用是对props
、methods
、data
、computed
、watch
等属性做初始化处理。 - 通过阅读源码,我们更加清楚的明白了在
beforeCreate
钩子的时候我们没有对props
、methods
、data
、computed
、watch
上的数据的访问权限。在created
中才可以。
beforeMount 和 mounted
// mountComponent 核心就是先实例化一个渲染Watcher
// 在它的回调函数中会调用 updateComponent 方法
// 两个核心方法 vm._render(生成虚拟Dom) 和 vm._update(映射到真实Dom)
// src/core/instance/lifecycle
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount') // 调用 beforeMount 钩子
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
// 将虚拟 Dom 映射到真实 Dom 的函数。
// vm._update 之前会先调用 vm._render() 函数渲染 VNode
...
const vnode = vm._render()
...
vm._update(vnode, hydrating)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
// 先判断是否 mouted 完成 并且没有被 destroyed
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted') //调用 mounted 钩子
}
return vm
}
通过上面的代码,我们可以看出在执行
vm._render()
函数渲染VNode
之前,执行了beforeMount
钩子函数,在执行完vm._update()
把VNode patch
到真实Dom
后,执行mounted
钩子。也就明白了为什么直到mounted
阶段才名正言顺的拿到了Dom
。
beforeUpdate 和 updated
// src/core/instance/lifecycle
new Watcher(vm, updateComponent, noop, {
before () {
// 先判断是否 mouted 完成 并且没有被 destroyed
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate') // 调用 beforeUpdate 钩子
}
}
}, true /* isRenderWatcher */)
// src/core/observer/scheduler
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
// 只有满足当前 watcher 为 vm._watcher(也就是当前的渲染watcher)
// 以及组件已经 mounted 并且没有被 destroyed 才会执行 updated 钩子函数。
callHook(vm, 'updated') // 调用 updated 钩子
}
}
}
- 第一段代码就是在beforeMount和mounted钩子中间出现的,那么watcher中究竟做了些什么呢?
- 第二段代码的callUpdatedHooks函数中什么时候才可以满足条件并执行updated呢?我们来接着往下看。
// src/instance/observer/watcher.js
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
// 在它的构造函数里会判断 isRenderWatcher,
// 接着把当前 watcher 的实例赋值给 vm._watcher
isRenderWatcher?: boolean
) {
// 还把当前 wathcer 实例 push 到 vm._watchers 中,
// vm._watcher 是专门用来监听 vm 上数据变化然后重新渲染的,
// 所以它是一个渲染相关的 watcher,因此在 callUpdatedHooks 函数中,
// 只有 vm._watcher 的回调执行完毕后,才会执行 updated 钩子函数
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
...
}
beforeDestroy 和 destroyed
// src/core/instance/lifecycle.js
// 在 $destroy 的执行过程中,它会执行 vm.__patch__(vm._vnode, null)
// 触发它子组件的销毁钩子函数,这样一层层的递归调用,
// 所以 destroy 钩子函数执行顺序是先子后父,和 mounted 过程一样。
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy') // 调用 beforeDestroy 钩子
vm._isBeingDestroyed = true
// 一些销毁工作
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// 拆卸 watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
...
vm._isDestroyed = true
// 调用当前 rendered tree 上的 destroy 钩子
// 发现子组件,会先去销毁子组件
vm.__patch__(vm._vnode, null)
callHook(vm, 'destroyed') // 调用 destroyed 钩子
// 关闭所有实例侦听器。
vm.$off()
// 删除 __vue__ 引用
if (vm.$el) {
vm.$el.__vue__ = null
}
// 释放循环引用
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
- 通过上面的代码,我们了解了组件销毁阶段的拆卸过程,其中会执行一个__patch__函数
- 除了这八种钩子外,我们在官网也可以查阅到另外几种不常用的钩子,这里列举出来
几种不常用的钩子
activated
keep-alive
组件激活时调用,该钩子在服务器端渲染期间不被调用。
deactivated
keep-alive
组件停用时调用,该钩子在服务器端渲染期间不被调用。
errorCaptured
- 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播
- 你可以在此钩子中修改组件的状态。因此在模板或渲染函数中设置其它内容的短路条件非常重要,它可以防止当一个错误被捕获时该组件进入一个无限的渲染循环。