深入理解Vue实例生命周期(中)

简介: 每个 Vue 实例在被创建时都会经过一系列的初始化过程。例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。 为了让开发者在Vue实例生命周期的不同阶段有机会插入自己的代码逻辑,vue提供了一种叫做生命周期钩子的函数。

DOM元素挂载


实例初始化完成后,会调用$mount开始DOM元素挂载。这个阶段会触发两个钩子函数:beforeMountmounted


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


// src/platforms/web/runtime/index.js
// 覆写$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  const options = this.$options
  if (!options.render) {
    let template = options.template
    // 选项中指定了template
    if (template) {
      if (typeof template === 'string') {
        // 如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        // 虽然在文档中并未说明,但template还可以指定一个DOM元素作为模板
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // 选项中指定了el
      template = getOuterHTML(el)
    }
    // 将模板解析成render函数
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // ...省略部分代码
  return mount.call(this, el, hydrating)
}


// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // 将对DOM元素的引用保存到$el
  vm.$el = el
  // ...省略部分代码
  // 调用beforeMount前需要执行模板编译逻辑
  callHook(vm, 'beforeMount')
  let updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false
  if (vm.$vnode == null) {
    // 标记为已挂载
    vm._isMounted = true
    // 触发mounted事件
    callHook(vm, 'mounted')
  }
  return vm
}


beforeMount


我们知道vue需要render函数来生成vnode。但是在实际开发中,基本都是通过templateel来指定模板,很少直接提供一个render函数。因此在触发beforeMount前,vue最重要的一个工作就是将HTML模板编译成render函数。beforeMount钩子函数被调用时,我们尚不能访问DOM元素。


mounted


每个vue实例都会对应一个render watcherrender watcher 会创建vnode(通过_render方法),并对vnode进行diff后,创建或者更新DOM元素(通过_update方法)。对于初次渲染来说,当创建完DOM元素后,把DOM树的根元素插入到body中,然后触发mounted钩子函数。此时,在钩子函数中可以对DOM元素进行操作了。


更新


实例完成初始化和挂载之后,如果由于用户的交互导致实例的状态发生了变化,实例将进入更新阶段。例如在代码中执行 this.msg = 'update msg',vue实例需要更新DOM元素。


实例的更新是异步的。前面提到过,render watcher 会负责调度程序创建vnode、创建更新DOM元素。当数据发生变化后,vue不会立即启动DOM的更新,而是先把实例对应的render watcher添加到一个队列中。然后在下一个事件循环中,统一执行DOM更新,清空队列。也就是调用下面代码中的flushSchedulerQueue函数。


此阶段会触发的钩子是:beforeUpdateupdated


/**
 * 清空所有的队列并执行watcher的更新逻辑
 */
function flushSchedulerQueue () {
  flushing = true
  let watcher, id
  // 队列按照watcher的id升序排序,目的是确保:
  // 1. 组件总是从父向子进行更新
  // 2. 用户创建的watcher先于渲染watcher更新
  // 3. 如果组件在父组件的watcher运行时被销毁,该组件的watcher可以跳过处理
  queue.sort((a, b) => a.id - b.id)
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    // 调用watcher.before,触发beforeUpdate钩子
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // 更新dom
    watcher.run()
  }
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  // 触发activated钩子
  callActivatedHooks(activatedQueue)
  // 触发updated钩子
  callUpdatedHooks(updatedQueue)
}
// 触发updated钩子
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) {
      callHook(vm, 'updated')
    }
  }
}


相关文章
|
3月前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
162 1
|
21天前
|
移动开发 JavaScript API
Vue Router 核心原理
Vue Router 是 Vue.js 的官方路由管理器,用于实现单页面应用(SPA)的路由功能。其核心原理包括路由配置、监听浏览器事件和组件渲染等。通过定义路径与组件的映射关系,Vue Router 将用户访问的路径与对应的组件关联,支持哈希和历史模式监听 URL 变化,确保页面导航时正确渲染组件。
|
25天前
|
监控 JavaScript 前端开发
ry-vue-flowable-xg:震撼来袭!这款基于 Vue 和 Flowable 的企业级工程项目管理项目,你绝不能错过
基于 Vue 和 Flowable 的企业级工程项目管理平台,免费开源且高度定制化。它覆盖投标管理、进度控制、财务核算等全流程需求,提供流程设计、部署、监控和任务管理等功能,适用于企业办公、生产制造、金融服务等多个场景,助力企业提升效率与竞争力。
83 12
|
21天前
|
JavaScript 前端开发 开发者
Vue中的class和style绑定
在 Vue 中,class 和 style 绑定是基于数据驱动视图的强大功能。通过 class 绑定,可以动态更新元素的 class 属性,支持对象和数组语法,适用于普通元素和组件。style 绑定则允许以对象或数组形式动态设置内联样式,Vue 会根据数据变化自动更新 DOM。
|
21天前
|
JavaScript 前端开发 数据安全/隐私保护
Vue Router 简介
Vue Router 是 Vue.js 官方的路由管理库,用于构建单页面应用(SPA)。它将不同页面映射到对应组件,支持嵌套路由、路由参数和导航守卫等功能,简化复杂前端应用的开发。主要特性包括路由映射、嵌套路由、路由参数、导航守卫和路由懒加载,提升性能和开发效率。安装命令:`npm install vue-router`。
|
2月前
|
JavaScript 安全 API
iframe嵌入页面实现免登录思路(以vue为例)
通过上述步骤,可以在Vue.js项目中通过 `iframe`实现不同应用间的免登录功能。利用Token传递和消息传递机制,可以确保安全、高效地在主应用和子应用间共享登录状态。这种方法在实际项目中具有广泛的应用前景,能够显著提升用户体验。
130 8
|
2月前
|
存储 设计模式 JavaScript
Vue 组件化开发:构建高质量应用的核心
本文深入探讨了 Vue.js 组件化开发的核心概念与最佳实践。
86 1
|
JavaScript
vue 的实例生命周期
vue 的实例生命周期
71 0
|
10月前
|
JavaScript 前端开发 开发者
vue实例、指令、生命周期
vue实例、指令、生命周期
71 1
|
10月前
|
JavaScript
vue 的实例生命周期
vue 的实例生命周期

热门文章

最新文章