iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 7
0
Modern Web

技術在走,Vue.js 要有系列 第 7

|D7| 從原始碼看 Vue 渲染機制 (5) - $mount

數據驅動是 vue 的核心概念之一,不直接操作 DOM 元素,而是通過修改 data,再把 data 渲染成 DOM。
$mount() 就是負責把 data 掛載到 Vue 實例渲染成 DOM。

compiler 時的 $mount

//vue/src/platforms/web/entry-runtime-with-compiler.js

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {

/*********************************************
 把 指定的 DOM 元素 賦值給 el 參數
*********************************************/
  el = el && query(el)

  const options = this.$options
  
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          ...
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        ...
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      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

      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

首先用 mount 變數把原本 Vue 實例上的 $mount 暫存起來,再重新定義 compiler 時的 $mount

  • 若 options 裡有 render 方法, 直接调用 mount.call(this, el, hydrating),即是調用 compiler 時的 $mount

  • 若 options 裡沒有 render 方法:

    1. 若有 template,則調用 compileToFunctions 編譯成 render 方法

    2. 若沒有 template,則判斷有沒有 el ,有就轉換成 template,再調用 compileToFunctions 編譯成 render 方法

    3. 把 render 方法掛載到 options 裡

    4. 調用 mount.call(this, el, hydrating),即是調用原本 Vue 實例上的 $mount

原本 Vue 實例上的 $mount

//src/platforms/web/runtime/index.js

// public mount method
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 上,
在 Vue 實例上定義 $mount,這裡的 $mount 是公用方法,可以在 runtime onlyruntime+compiler 時使用

mountComponent

// src/core/instance/lifecycle.js

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {

/*********************************************
 把 el 參數 賦值給 vm.$el
*********************************************/
  vm.$el = el
  
  /*********************************************
     若沒有 render 方法,把 createEmptyVNode 作為  render 方法
  *********************************************/
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      ...
  }
  callHook(vm, 'beforeMount')

  let updateComponent

  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
  /*********************************************
     定義 updateComponent 方法,執行 vm._render 返回 vnode,vnode 當參數傳入 vm._update 中,渲染成真實的 DOM
  *********************************************/
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true )
  hydrating = false

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

上一篇
|D6| 從原始碼看 Vue 渲染機制 (4) - render
下一篇
|D8| 從原始碼看 Vue 元件化 (1) - 概念介紹
系列文
技術在走,Vue.js 要有30

尚未有邦友留言

立即登入留言