lifecycle.png

callHook

执行生命周期的函数都是调用callHook方法
定义在src/core/instance/lifecycle中

  1. export function callHook (vm: Component, hook: string) {
  2. // #7573 disable dep collection when invoking lifecycle hooks
  3. pushTarget()
  4. // 根据传入的字符串hook,拿到对应的回调函数数组
  5. const handlers = vm.$options[hook]
  6. const info = `${hook} hook`
  7. if (handlers) {
  8. // 遍历执行且把vm作为函数执行的上下文
  9. for (let i = 0, j = handlers.length; i < j; i++) {
  10. invokeWithErrorHandling(handlers[i], vm, null, vm, info)
  11. }
  12. }
  13. if (vm._hasHookEvent) {
  14. vm.$emit('hook:' + hook)
  15. }
  16. popTarget()
  17. }

callhook函数功能就是调用某个生命周期钩子注册的所有回调函数

beforeCreate & created

在实例化Vue阶段,在_init方法中执行
定义在src/core/instance/init.js中

  1. Vue.prototype._init = function (options?: Object) {
  2. // ...
  3. vm._self = vm
  4. initLifecycle(vm)
  5. initEvents(vm)
  6. initRender(vm)
  7. callHook(vm, 'beforeCreate') // 获取不到props、data中定义的值,也不能调用methods中定义的函数
  8. initInjections(vm) // resolve injections before data/props
  9. initState(vm) // 初始化props、data、methods、watch、computed
  10. initProvide(vm) // resolve provide after data/props
  11. callHook(vm, 'created')
  12. // ...
  13. }

这两个钩子函数执行的时候并没有渲染DOM,所以也不能够访问DOM
一般来说如果组件在加载的时候需要和后端做交互,放在这两个钩子函数执行都可以
如果要访问props、data等数据的话就需要使用created钩子函数
vue-router和vuex都混合了beforeCreate钩子函数

beforeMount & mounted

beforeMount钩子函数发生在mount,也就是DOM挂载之前,是在mountComponent函数中调用的
定义在src/core/instance/lifecycle.js中

  1. export function mountComponent (
  2. vm: Component,
  3. el: ?Element,
  4. hydrating?: boolean
  5. ): Component {
  6. vm.$el = el
  7. if (!vm.$options.render) {
  8. vm.$options.render = createEmptyVNode
  9. if (process.env.NODE_ENV !== 'production') {
  10. /* istanbul ignore if */
  11. if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
  12. vm.$options.el || el) {
  13. warn(
  14. 'You are using the runtime-only build of Vue where the template ' +
  15. 'compiler is not available. Either pre-compile the templates into ' +
  16. 'render functions, or use the compiler-included build.',
  17. vm
  18. )
  19. } else {
  20. warn(
  21. 'Failed to mount component: template or render function not defined.',
  22. vm
  23. )
  24. }
  25. }
  26. }
  27. callHook(vm, 'beforeMount')
  28. let updateComponent
  29. /* istanbul ignore if */
  30. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  31. updateComponent = () => {
  32. const name = vm._name
  33. const id = vm._uid
  34. const startTag = `vue-perf-start:${id}`
  35. const endTag = `vue-perf-end:${id}`
  36. mark(startTag)
  37. const vnode = vm._render()
  38. mark(endTag)
  39. measure(`vue ${name} render`, startTag, endTag)
  40. mark(startTag)
  41. vm._update(vnode, hydrating)
  42. mark(endTag)
  43. measure(`vue ${name} patch`, startTag, endTag)
  44. }
  45. } else {
  46. updateComponent = () => {
  47. vm._update(vm._render(), hydrating)
  48. }
  49. }
  50. // we set this to vm._watcher inside the watcher's constructor
  51. // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  52. // component's mounted hook), which relies on vm._watcher being already defined
  53. new Watcher(vm, updateComponent, noop, {
  54. before () {
  55. if (vm._isMounted && !vm._isDestroyed) {
  56. callHook(vm, 'beforeUpdate')
  57. }
  58. }
  59. }, true /* isRenderWatcher */)
  60. hydrating = false
  61. // manually mounted instance, call mounted on self
  62. // mounted is called for render-created child components in its inserted hook
  63. // vm.$vnode == null成立表面这不是一次组件的初始化过程,而是通过外部new Vue初始化的过程
  64. if (vm.$vnode == null) {
  65. vm._isMounted = true
  66. callHook(vm, 'mounted')
  67. }
  68. return vm
  69. }

在执行vm._render()函数渲染VNode之前,执行了beforeMount钩子函数,在执行完vm._update()把VNode patch到真实DOM后执行mounted钩子

组件的mounted时机

组件的VNode patch到DOM后会执行invokeInsertHook函数,把insertedVnodeQueue里保存的钩子函数依次执行一遍
定义在src/core/vdom/patch.js中

  1. function invokeInsertHook (vnode, queue, initial) {
  2. // delay insert hooks for component root nodes, invoke them after the
  3. // element is really inserted
  4. if (isTrue(initial) && isDef(vnode.parent)) {
  5. vnode.parent.data.pendingInsert = queue
  6. } else {
  7. for (let i = 0; i < queue.length; ++i) {
  8. queue[i].data.hook.insert(queue[i])
  9. }
  10. }
  11. }

对于组件,insert钩子函数定义在src/core/vdom/create-component.js中

  1. const componentVNodeHooks = {
  2. // ...
  3. insert (vnode: MountedComponentVNode) {
  4. const { context, componentInstance } = vnode
  5. if (!componentInstance._isMounted) {
  6. componentInstance._isMounted = true
  7. // 子组件执行mounted钩子函数
  8. callHook(componentInstance, 'mounted')
  9. }
  10. if (vnode.data.keepAlive) {
  11. if (context._isMounted) {
  12. // vue-router#1212
  13. // During updates, a kept-alive component's child components may
  14. // change, so directly walking the tree here may call activated hooks
  15. // on incorrect children. Instead we push them into a queue which will
  16. // be processed after the whole patch process ended.
  17. queueActivatedComponent(componentInstance)
  18. } else {
  19. activateChildComponent(componentInstance, true /* direct */)
  20. }
  21. }
  22. },
  23. // ...
  24. }

对于同步渲染的子组件而言,mounted钩子函数的执行顺序是先子后父

beforeUpdate & updated

在数据更新时执行beforeUpdate和updated钩子函数
beforeUpdate的执行时机是在渲染Watcher的before函数中

  1. export function mountComponent (
  2. vm: Component,
  3. el: ?Element,
  4. hydrating?: boolean
  5. ): Component {
  6. // ...
  7. // we set this to vm._watcher inside the watcher's constructor
  8. // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  9. // component's mounted hook), which relies on vm._watcher being already defined
  10. new Watcher(vm, updateComponent, noop, {
  11. before () {
  12. // 组件mounted之后才会调用这个钩子函数
  13. if (vm._isMounted) {
  14. callHook(vm, 'beforeUpdate')
  15. }
  16. }
  17. }, true /* isRenderWatcher */)
  18. // ...
  19. }

update执行时机是在flushSchedulerQueue函数调用的时候
定义在src/core/observer/scheduler.js中

  1. /**
  2. * Flush both queues and run the watchers.
  3. */
  4. function flushSchedulerQueue () {
  5. currentFlushTimestamp = getNow()
  6. flushing = true
  7. let watcher, id
  8. // Sort queue before flush.
  9. // This ensures that:
  10. // 1. Components are updated from parent to child. (because parent is always
  11. // created before the child)
  12. // 2. A component's user watchers are run before its render watcher (because
  13. // user watchers are created before the render watcher)
  14. // 3. If a component is destroyed during a parent component's watcher run,
  15. // its watchers can be skipped.
  16. queue.sort((a, b) => a.id - b.id)
  17. // do not cache length because more watchers might be pushed
  18. // as we run existing watchers
  19. for (index = 0; index < queue.length; index++) {
  20. watcher = queue[index]
  21. if (watcher.before) {
  22. watcher.before()
  23. }
  24. id = watcher.id
  25. has[id] = null
  26. watcher.run()
  27. // in dev build, check and stop circular updates.
  28. if (process.env.NODE_ENV !== 'production' && has[id] != null) {
  29. circular[id] = (circular[id] || 0) + 1
  30. if (circular[id] > MAX_UPDATE_COUNT) {
  31. warn(
  32. 'You may have an infinite update loop ' + (
  33. watcher.user
  34. ? `in watcher with expression "${watcher.expression}"`
  35. : `in a component render function.`
  36. ),
  37. watcher.vm
  38. )
  39. break
  40. }
  41. }
  42. }
  43. // keep copies of post queues before resetting state
  44. const activatedQueue = activatedChildren.slice()
  45. // 更新了的watcher数组
  46. const updatedQueue = queue.slice()
  47. resetSchedulerState()
  48. // call component updated and activated hooks
  49. callActivatedHooks(activatedQueue)
  50. callUpdatedHooks(updatedQueue)
  51. // devtool hook
  52. /* istanbul ignore if */
  53. if (devtools && config.devtools) {
  54. devtools.emit('flush')
  55. }
  56. }
  57. function callUpdatedHooks (queue) {
  58. let i = queue.length
  59. while (i--) {
  60. const watcher = queue[i]
  61. const vm = watcher.vm
  62. // 当前watcher为vm._watcher,且组件已经mounted
  63. if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
  64. callHook(vm, 'updated')
  65. }
  66. }
  67. }

在组建mount的过程中会实例化一个渲染的Watcher去监听vm上的数据变化重新渲染,发生在mountComponent函数执行时

  1. export function mountComponent (
  2. vm: Component,
  3. el: ?Element,
  4. hydrating?: boolean
  5. ): Component {
  6. // ...
  7. let updateComponent
  8. /* istanbul ignore if */
  9. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  10. updateComponent = () => {
  11. const name = vm._name
  12. const id = vm._uid
  13. const startTag = `vue-perf-start:${id}`
  14. const endTag = `vue-perf-end:${id}`
  15. mark(startTag)
  16. const vnode = vm._render()
  17. mark(endTag)
  18. measure(`vue ${name} render`, startTag, endTag)
  19. mark(startTag)
  20. vm._update(vnode, hydrating)
  21. mark(endTag)
  22. measure(`vue ${name} patch`, startTag, endTag)
  23. }
  24. } else {
  25. updateComponent = () => {
  26. vm._update(vm._render(), hydrating)
  27. }
  28. }
  29. // we set this to vm._watcher inside the watcher's constructor
  30. // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  31. // component's mounted hook), which relies on vm._watcher being already defined
  32. new Watcher(vm, updateComponent, noop, {
  33. before () {
  34. if (vm._isMounted && !vm._isDestroyed) {
  35. callHook(vm, 'beforeUpdate')
  36. }
  37. }
  38. }, true /* isRenderWatcher */)
  39. // ...
  40. }

判断isRenderWatcher,把当前watcher的实例赋值给vm_watcher
定义在src/core/observer/watcher.js中

  1. export default class Watcher {
  2. // ...
  3. constructor (
  4. vm: Component,
  5. expOrFn: string | Function,
  6. cb: Function,
  7. options?: ?Object,
  8. isRenderWatcher?: boolean
  9. ) {
  10. this.vm = vm
  11. // 判断isRenderWatcher,把当前watcher的实例赋值给vm_watcher
  12. if (isRenderWatcher) {
  13. vm._watcher = this
  14. }
  15. // vm._watcher专门用来监听vm上数据变化然后重新渲染,与渲染相关的watcher
  16. vm._watchers.push(this)
  17. // ...
  18. }
  19. }

callUpdatedHooks函数中只有vm._watcher的回调函数执行完毕后才会执行updated钩子函数

beforeDestroy & destroyed

beforeDestroy 和 destroyed 钩子函数的执行时机在组件销毁的阶段,它们最终会调用$destroy方法
定义在src/core/instance/lifecycle.js中

  1. Vue.prototype.$destroy = function () {
  2. const vm: Component = this
  3. if (vm._isBeingDestroyed) {
  4. return
  5. }
  6. callHook(vm, 'beforeDestroy')
  7. vm._isBeingDestroyed = true
  8. // remove self from parent
  9. // 从parent的$.children中删除自身
  10. const parent = vm.$parent
  11. if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
  12. remove(parent.$children, vm)
  13. }
  14. // teardown watchers
  15. // 删除watcher
  16. if (vm._watcher) {
  17. vm._watcher.teardown()
  18. }
  19. let i = vm._watchers.length
  20. while (i--) {
  21. vm._watchers[i].teardown()
  22. }
  23. // remove reference from data ob
  24. // frozen object may not have observer.
  25. if (vm._data.__ob__) {
  26. vm._data.__ob__.vmCount--
  27. }
  28. // call the last hook...
  29. vm._isDestroyed = true
  30. // invoke destroy hooks on current rendered tree
  31. // 当前渲染的VNode执行销毁钩子函数
  32. vm.__patch__(vm._vnode, null)
  33. // fire destroyed hook
  34. callHook(vm, 'destroyed')
  35. // turn off all instance listeners.
  36. vm.$off()
  37. // remove __vue__ reference
  38. if (vm.$el) {
  39. vm.$el.__vue__ = null
  40. }
  41. // release circular reference (#6759)
  42. if (vm.$vnode) {
  43. vm.$vnode.parent = null
  44. }
  45. }

vm.patch(vm._vnode, null)触发它子组件的销毁钩子函数,这样一层层的递归调用,所以destroy钩子函数执行顺序是先子后父

activated & deactivated

专门为keep-alive组件定制的钩子,后续介绍