前言
在Vue.js 2.0中,组件的生命周期函数分为8个阶段,分别是:
- beforeCreate:组件实例刚被创建,但是数据和事件监听还未被初始化。
- created:组件实例已经被创建,并且数据和事件监听已经初始化完成。
- beforeMount:组件将要被挂载到页面上,但是还没有开始渲染。
- mounted:组件已经被挂载到页面上,并且已经渲染完毕。
- beforeUpdate:组件将要被更新,但是还没有开始重新渲染。
- updated:组件已经被更新,并且重新渲染完毕。
- beforeDestroy:组件将要被销毁,但是还没有开始销毁。
- destroyed:组件已经被销毁。
实现过程
在Vue.js 2.0中,生命周期函数的实现主要是通过一个叫做“lifecycle.js”的模块来完成的。下面是该模块的完整代码,包括注释:
/* @flow */ import config from '../config' import { callHook, activateChildComponent } from '../instance/lifecycle' import { createEmptyVNode } from '../vdom/vnode' import { updateComponentListeners } from '../vdom/helpers/index' export function lifecycleMixin (Vue: Class<Component>) { Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = config.activeInstance config.activeInstance = vm vm._vnode = vnode // Vue.js 的更新流程 if (!prevVnode) { // 如果没有旧的 VNode,说明这是组件的首次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // 如果有旧的 VNode,说明这是组件的更新过程 vm.$el = vm.__patch__(prevVnode, vnode) } config.activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm // 如果组件已经被挂载,并且有子组件,则需要激活子组件的钩子函数 if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { activateChildComponent(vm.$vnode, vm.$options._renderChildren, vm) } // 触发组件的 updated 钩子函数 callHook(vm, 'updated') } } Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } // 触发组件的 beforeDestroy 钩子函数 callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // 如果组件有父组件,则需要将其从父组件的 $children 中删除 if (vm.$parent) { const index = vm.$parent.$children.indexOf(vm) if (index > -1) { vm.$parent.$children.splice(index, 1) } } // 销毁组件上的所有监听器 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 // 触发组件的 destroyed 钩子函数 callHook(vm, 'destroyed') Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject } // 设置父节点,用于在子组件中使用 $parent vm.$vnode = _parentVnode // 渲染 VNode let vnode try { // 调用渲染函数,得到组件的 VNode vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) vnode = vm._vnode || createEmptyVNode() } // 如果返回的是一个数组,则只取第一个元素作为 VNode if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // 判断是否为合法的 VNode,如果不是,则创建一个空的 VNode if (!(vnode instanceof VNode)) { vnode = createEmptyVNode() } // 设置组件的父子关系 vnode.parent = _parentVnode return vnode } } // Vue 实例的生命周期钩子函数 export function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } // 设置当前实例的 $parent、$root、$children 属性 vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false } export function lifecycleMixin (Vue: Class<Component>) { Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this // 如果已经渲染过了,则会调用 beforeUpdate 钩子函数 if (vm._isMounted) { callHook(vm, 'beforeUpdate') } // 保存旧的 VNode const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // 如果之前没有 VNode,则直接渲染 if (!prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // 如果有旧的 VNode,则对比新旧 VNode,然后更新 vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // 更新完毕之后,会调用 updated 钩子函数 if (prevEl) { vm.$forceUpdate() callHook(vm, 'updated') } else if (vm.$vnode == null) { callHook(vm, 'mounted') vm._isMounted = true } return vm } Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } Vue.prototype.$destroy = function () { const vm: Component = this // 如果正在被销毁,则直接返回 if (vm._isBeingDestroyed) { return } // 触发 beforeDestroy 钩子函数 callHook(vm, 'beforeDestroy') // 标记当前实例正在被销毁 vm._isBeingDestroyed = true // 从父组件的 $children 数组中移除当前实例 const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // 卸载所有的 watcher if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // 使当前实例处于销毁状态 vm._isDestroyed = true // 触发 destroyed 钩子函数 callHook(vm, 'destroyed') // 释放所有占用的资源 vm.$off() // 卸载所有子组件 if (vm.$options._parentElm && vm.$options._refElm) { vm.$options._parentElm.removeChild(vm.$options._refElm) } // 解除父子关系 vm.$parent = null vm.$root = null // 移除所有监听器 vm.$listeners = {} // 移除所有实例属性 vm._watchers = [] vm._data = {} vm._props = {} vm._methods = {} vm._computedWatchers = {} // 标记当前实例已被销毁 vm._renderProxy = null vm._staticTrees = null // 禁用当前实例 vm._disable = true } } // 触发钩子函数 export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } }
在上面的代码中,我们可以看到 initLifecycle 函数会设置当前实例的 $parent、$root、$children 和 $refs 属性。它还会查找当前实例的第一个非抽象父组件,并将当前实例添加到父组件的 $children 数组中。lifecycleMixin 函数实现了 Vue 实例的更新、强制更新和销毁。它还定义了 callHook 函数来触发生命周期钩子函数。
总结
Vue.js 的生命周期钩子函数是在 Vue 实例的不同阶段执行的回调函数。通过使用这些钩子函数,我们可以在 Vue 实例的不同生命周期阶段执行自定义代码。
Vue 2.x 的生命周期钩子函数包括 beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy 和 destroyed。这些钩子函数按照执行顺序分为三个阶段:创建阶段、挂载阶段和销毁阶段。
Vue 3.x 中的生命周期钩子函数与 Vue 2.x 中的类似,但是 beforeCreate 和 created 钩子函数的实现有所不同。Vue 3.x 使用 setup 函数来代替 beforeCreate 和 created 钩子函数。
在 Vue.js 源码中,生命周期钩子函数的实现是在 lifecycleMixin 函数中完成的。lifecycleMixin 函数实现了 Vue 实例的更新、强制更新和销毁,并定义了 callHook 函数来触发生命周期钩子函数。
通过深入理解 Vue.js 的生命周期钩子函数实现,我们可以更好地理解 Vue.js 的运行机制,并且可以在自己的 Vue.js 项目中更加灵活地使用生命周期钩子函数来实现自定义功能。