xml地图|网站地图|网站标签 [设为首页] [加入收藏]

详解Vue源码学习之callHook钩子函数,Vue源码探究

来源:http://www.ccidsi.com 作者:集成经验 人气:158 发布时间:2019-07-24
摘要:Vue实例在分化的生命周期阶段,都调用了callHook方法。举例在实例初步化(_init)的时候调用callHook(vm,'beforeCreate')和callHook(vm, 'created')。 本篇代码位于vue/src/core/instance/lifecycle.js 新禧佳节

Vue实例在分化的生命周期阶段,都调用了callHook方法。举例在实例初步化(_init)的时候调用callHook(vm, 'beforeCreate')和callHook(vm, 'created')。

本篇代码位于vue/src/core/instance/lifecycle.js

新禧佳节后续写博客~加油!

图片 1

开班查究完了大旨类的落到实处之后,接下去将在先导深远到Vue完结的实际职能部分了。在全部的作用起先运维此前,要来精晓一下Vue的生命周期,在起首化函数中负有作用模块绑定到Vue的核心类上事先,初叶开端推行了三个开始化生命周期的函数initLifecycle,先来探访那些函数做了些什么。

本次来学学一下Vue的生命周期,看看生命周期是怎么回事。

此间的"beforeCreate","created"状态并不是自由定义,而是来自于Vue内部的定义的生命周期钩子。

// 导出initLifecycle函数,接受一个Component类型的vm参数export function initLifecycle (vm: Component) { // 获取实例的$options属性,赋值为options变量 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为undefined vm.$parent = parent // 设置根属性,没有父级则为实例对象自身 vm.$root = parent ? parent.$root : vm // 初始化$children和$refs属性 // vm.$children是子组件的数组集合 // vm.$refs是指定引用名称的组件对象集合 vm.$children = [] vm.$refs = {} // 初始化一些私有属性 // 初始化watcher vm._watcher = null // _inactive和_directInactive是判断激活状态的属性 vm._inactive = null vm._directInactive = false // 生命周期相关的私有属性 vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false}

callHook

生命周期重要就是在源码有个别时刻点实施那一个 callHook 方法来调用 vm.$options 的生命周期钩子方法(假若定义了生命周期钩子方法的话)。
咱俩来看看 callHook 代码:

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook] // 获取Vue选项中的生命周期钩子函数
  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)
  }
}

譬如触发 mounted 钩子的方式:

callHook(vm, 'mounted')
var LIFECYCLE_HOOKS = [
 'beforeCreate',
 'created',
 'beforeMount',
 'mounted',
 'beforeUpdate',
 'updated',
 'beforeDestroy',
 'destroyed',
 'activated',
 'deactivated',
 'errorCaptured'
];

initLifecycle 函数极其老妪能解,主要是在生命周期初叶在此以前安装有个别城门失火的属性的初叶值。一些品质将要其后的生命周期运维时期接纳到。

生命周期钩子

先上一张图看下Vue的生命周期,大家能够在相应的生命周期中定义一些平地风波。

图片 2

Vue生命周期

再钻探Vue官方网站的生命周期图示,是否更易于明白了。

生命周期的始发除了安装了相关属性的开始值之外,还为类原型对象挂载了有个别办法,包罗个人的立异组件的方法和公用的生命周期相关的法子。这么些办法都带有在 lifecycleMixin 函数中,还记得那也是在概念宗旨类之后施行的那多少个函数之一,也来拜望它的内容。

beforeCreate & created

先看看那多少个格局调用的小时。

beforeCreate
在实例起初化之后,数据观测 (data observer) 和 event/watcher 事件配置在此以前被调用。
created
在实例创造达成后被随即调用。在这一步,实例已成功以下的配置:数据观测 (data observer),属性和艺术的运算,watch/event 事件回调。但是,挂载阶段还没起来,$el 属性近年来不可知。

实际代码如下

  // src/core/instance/init.js
  Vue.prototype._init = function (options?: Object) {
    ……
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件
    initRender(vm) // 初始化渲染
    callHook(vm, 'beforeCreate')
    initInjections(vm) // 初始化Inject
    initState(vm) // 初始化数据
    initProvide(vm) // 初始化Provide
    callHook(vm, 'created')
    ……
    if (vm.$options.el) {
      vm.$mount(vm.$options.el) // 如果有el属性,将内容挂载到el中去。
    }
  }

图片 3

// 导出lifecycleMixin函数,接收形参Vue,// 使用Flow进行静态类型检查指定为Component类export function lifecycleMixin (Vue: Class<Component>) { // 为Vue原型对象挂载_update私有方法 // 接收vnode虚拟节点类型参数和一个可选的布尔值hydrating Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { // 定义实例变量 const vm: Component = this // 下面三条赋值操作主要是为了存储属性 // 实例的$el属性赋值给prevEl变量,这是新传入的实例挂载元素 const prevEl = vm.$el // 实例的_vnode属性赋值给prevVnode变量,储存的旧虚拟节点 const prevVnode = vm._vnode // 将activeInstance赋值给prevActiveInstance变量,激活实例 // activeInstance初始为null const prevActiveInstance = activeInstance // 下面是针对新属性的赋值 // 将新实例设置为activeInstance activeInstance = vm // 将传入的vnode赋值给实例的_vnode属性 // vnode是新生成的虚拟节点数,这里把它储存起来覆盖 vm._vnode = vnode // 下面使用到的Vue.prototype .__ patch__方法是在运行时里注入的 // 根据运行平台的不同定义 // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. // 如果prevVnode属性不存在说明是新创建实例 // 执行实例属性$el的初始化渲染,否则更新节点 if (!prevVnode) { // 如果旧的虚拟节点不存在则调用patch方法 // 传入挂载的真实DOM节点和新生成的虚拟节点 // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // 否则执行虚拟节点更新操作,传入的是新旧虚拟节点 // updates vm.$el = vm.__patch__(prevVnode, vnode) } // 将之前的激活实例又赋值给activeInstance activeInstance = prevActiveInstance // 更新__vue__属性的引用 // update __vue__ reference // 如果存在旧元素则设置它的__vue__引用为null if  { prevEl.__vue__ = null } // 如果实例的$el属性存在,设置它的__vue__引用为该实例 if  { vm.$el.__vue__ = vm } // 如果父节点是一个高阶组件,也更新它的元素节点 // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // 更新的钩子由调度器调用,以确保在父更新的钩子中更新子项。 // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. } // 为Vue实例挂载$forceUpdate方法,实现强制更新 Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } // 为Vue实例挂载$destroy方法 Vue.prototype.$destroy = function () { // 定义实例变量 const vm: Component = this // 如果实例已经在销毁中,则返回 if (vm._isBeingDestroyed) { return } // 调用beforeDestroy钩子 callHook(vm, 'beforeDestroy') // 给实例设置正在销毁中的标志 vm._isBeingDestroyed = true // 从父组件中移除自身 // remove self from parent const parent = vm.$parent // 如果非抽象父级组件存在且没有在销毁中,则从父组件中移除实例 if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // 销毁所有观察器 // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while  { vm._watchers[i].teardown() } // 移除对象引用 // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // 调用最后的钩子 // call the last hook... // 设置实例的已销毁标志 vm._isDestroyed = true // 调用当前渲染树上的销毁钩子 // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // 触发销毁钩子 // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. // 清除所有监听事件 vm.$off() // 移除实例引用 // remove __vue__ reference if  { vm.$el.__vue__ = null } // 释放循环引用 // release circular reference  if (vm.$vnode) { vm.$vnode.parent = null } }}

beforeMount & mounted

beforeMount
在挂载开头从前被调用:相关的 render 函数第叁次被调用。该钩子在劳动器端渲染时期不被调用。
mounted
el 被新成立的 vm.$el 替换,并挂载到实例上去然后调用该钩子。倘若 root 实例挂载了二个文书档案内元素,当 mounted 被调用时 vm.$el 也在文书档案内。

贴出代码逻辑

// src/core/instance/lifecycle.js
// 挂载组件的方法
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

那么那个 mountComponent 在哪儿用了啊?正是在Vue的 $mount 方法中运用。

// src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

最后会在Vue初阶化的时候,判别是或不是有 el,借使有则进行 $mount 方法。

// src/core/instance/init.js
if (vm.$options.el) {
  vm.$mount(vm.$options.el) // 如果有el属性,将内容挂载到el中去。
}

从那之后生命周期逻辑应该是 beforeCreate - created - beforeMount -mounted

接下去大家来看一下Vue中达成钩子函数的源码:

lifecycleMixin 函数完结了多个原型承袭方法:

beforeUpdate & updated

beforeUpdate
数据更新时调用,爆发在编造 DOM 打补丁在此以前。这里适合在创新从前访问现成的 DOM,比如手动移除已增多的平地风波监听器。
updated
鉴于数量变动导致的设想 DOM 重新渲染和打补丁,在那之后会调用该钩子。

找代码逻辑~ beforeUpdate 和 updated 在多少个地点调用。

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    // 如果是已经挂载的,就触发beforeUpdate方法。
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    ……
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

在执行 _update 方法的时候,若是 DOM 已经挂载了,则调用 beforeUpdate 方法。
在 _update 方法的最后我也注视了调用 updated hook 的岗位:updated 钩子由 scheduler 调用来确定保证子组件在三个父组件的 update 钩子中
小编们找到 scheduler,开掘有个 callUpdateHooks 方法,该措施遍历了 watcher 数组。

// src/core/observer/scheduler.js
function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated')
    }
  }
}

这个 callUpdatedHooksflushSchedulerQueue 方法中调用。

/**
 * 刷新队列并运行watcher
 */
function flushSchedulerQueue () {
  flushing = true
  let watcher, id
  queue.sort((a, b) => a.id - b.id)

  for (index = 0; index < queue.length; index  ) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    watcher.run()
  }

  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // 调用组件的updated和activated生命周期
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}

接二连三找下去

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true // 此参数用于判断watcher的ID是否存在
    ……
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

最终在 watcher.js 找到 update 方法:

  // src/core/observer/watcher.js
  update () {
    // lazy 懒加载
    // sync 组件数据双向改变
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this) // 排队watcher
    }
  }

相当于是队列实施完 Watcher 数组的 update 方法后调用了 updated 钩子函数。

本文由68399皇家赌场发布于集成经验,转载请注明出处:详解Vue源码学习之callHook钩子函数,Vue源码探究

关键词: 68399皇家赌场 VUE 源码 生命周期 Vue实验室

最火资讯