Vue中render的流程

從我的上兩篇文章已經(jīng)講了Vue的數(shù)據(jù)響應(yīng)了,那么怎么render和re-render呢?
我看的是2.1.10這個版本的,init.jsinitMixin()的初始化代碼是這樣寫的

    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initState(vm)  
    callHook(vm, 'created')
    //下面這句代碼是真正開始渲染的部分
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }

其實在initRender(vm)函數(shù)中就做了一些渲染的初始化工作,我后面會具體講。
Vue中有兩個地方定義過Vue.prototype.$mount。一個是在web-runtime.js一個是在web-runtime-with-compile.js,這里就涉及到Vue的兩種構(gòu)建方式。獨立構(gòu)建和運行時構(gòu)建。

獨立構(gòu)建和運行時構(gòu)建

獨立構(gòu)建和運行時構(gòu)建的區(qū)別是,前者包括模板編譯,后者不包含。
模板編譯是將模板字符串編譯成渲染函數(shù)。Vue的渲染包括兩步,第一是編譯過程,是將字符串模板編譯成render函數(shù);第二運行過程,是運行render函數(shù)。
獨立構(gòu)建會執(zhí)行template到render函數(shù),支持template選項,但是依賴瀏覽器接口,所以不能用來作為服務(wù)器端渲染。
運行時構(gòu)建不包括編譯部分,直接執(zhí)行render函數(shù),所以不支持template選項,但是因為沒有編譯過程,讓框架更輕量。
雖然運行時不能寫template選項,但是單文件組件中還是可以寫模板,通常我們會用vue-loader 和 vueify 預(yù)編譯模板,最終只用打包運行時,而不用打包編譯器。

現(xiàn)在回過來看??代碼
web-runtime-with-compile.js

//緩存了'./web-runtime'中的Vue.prototype.$mount
const mount = Vue.prototype.$mount

//然后重新覆蓋了Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  //生成el的dom
  el = el && query(el)
  const options = this.$options
  // resolve template/el and convert to render function
 //如果沒有render就將template轉(zhuǎn)為render
  if (!options.render) {
    let template = options.template
    if (template) {
        template = idToTemplate(template)
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      //直接把el中的整個代碼以字符串賦給template
      template = getOuterHTML(el)
    }
    //這里開始編譯 template
    if (template) {
      const { render, staticRenderFns } =compileToFunctions(template, {
        warn: msg => warn(msg, this),
        shouldDecodeNewlines,
        delimiters: options.delimiters
      }, this)
      //將compileToFunctions生成的render給this.$options
      options.render = render
    }
  }
  //用緩存了'./web-runtime'中的Vue.prototype.$mount來執(zhí)行
  //然額'./web-runtime'中的Vue.prototype.$mount'用的是lifecycle.js中的_mount
  return mount.call(this, el, hydrating)
}

分析一遍:
首先緩存了./web-runtime也就是運行時構(gòu)建中定義的Vue.prototype.$mount方法,然后重新定義該方法,對有render選項的直接調(diào)用緩存的$mount(),如果沒有render方法,就檢查是否有template選項,如果有就將template編譯成render,如果沒有就將el中的代碼轉(zhuǎn)成字符串,然后進(jìn)行編譯生成render。
可以看出,最終都是生成render函數(shù),然后調(diào)用緩存下來的mount函數(shù)。下面在看mount函數(shù)。
web-runtime.js

Vue.prototwype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  /**
   * query => 檢查el是否在頁面中可以找到,
   * 如果找不到就create一個div,
   * 如果找到了,就返回document.querySelector(el)
   */
  el = el && inBrowser ? query(el) : undefined
  //這里的_mount方法在lifecycle.js中定義
  return this._mount(el, hydrating)
}

可以看出,最終都將調(diào)用lifecycle.js中定義的_mount(),那繼續(xù)看這個方法

Vue.prototype._mount = function (
    el?: Element | void,
    hydrating?: boolean
  ): Component {
    const vm: Component = this
    //實例上添加$el
    vm.$el = el
    //執(zhí)行beforeMount生命周期鉤子
    callHook(vm, 'beforeMount')

    vm._watcher = new Watcher(vm, function updateComponent () {
      vm._update(vm._render(), hydrating)
    }, noop)

    hydrating = false
    // manually mounted instance, call mounted on self
    // mounted is called for render-created child components in its inserted hook
    //如果第一次執(zhí)行mount就觸發(fā)mounted生命周期鉤子
    if (vm.$vnode == null) {
      vm._isMounted = true
      callHook(vm, 'mounted')
    }
    return vm
  }

額。。。我覺得上面的注釋還是寫得挺清楚的。這里重點講一下這一句

vm._watcher = new Watcher(vm, function updateComponent () {
      vm._update(vm._render(), hydrating)
}, noop)

根據(jù)之前數(shù)據(jù)響應(yīng)部分已經(jīng)講過Watcher這個構(gòu)造函數(shù)。Watcher在創(chuàng)建實例的時候會觸發(fā)get(),這里get就是執(zhí)行updateComponent () => vm._update() => vm.render(),可以看出最先執(zhí)行的是vm.render()。

Vue.prototype._render = function (): VNode {
    const vm: Component = this

    //解構(gòu)$options中的render
    //$options.render是compileToFunctions將template或者el編譯出來的
    const {
      render,
      staticRenderFns,
      _parentVnode
    } = vm.$options
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      vnode = render.call(vm._renderProxy, vm.$createElement)
      ...
    } catch (e) {
      ...
      vnode = vm._vnode
    }
    return vnode
  }
vm._renderProxy = vm

首先很容易看出_render()方法返回的是一個vnode,該方法中調(diào)用的是 render。render打印出來看是這樣的函數(shù)

function anonymous() {
  with(this){
    return _c('div',{attrs:{"id":"app"} },
                    [ _v("\n    "+_s(sum)+"\n  ")]                 
    )}
}

首先,可以知道這個函數(shù)中的this指向了vm本身。_c,_v,_s,sum都是vm的方法和屬性。在獲取this.sum就會觸發(fā)definePropery()get()。好了,現(xiàn)在可以將render和數(shù)據(jù)響應(yīng)關(guān)聯(lián)起來了。在執(zhí)行new Watcher(updateComponent())的時候收集依賴,在依賴的值改變的時候,觸發(fā)watcher的updateComponent,重新收集依賴。
其實在template轉(zhuǎn)為render的時候,中間是先生成的抽象語法樹(AST)提取靜態(tài)節(jié)點,然后再轉(zhuǎn)為render。并且還有vnode的patch算法,在這篇文章還沒有講到。如果后面代碼我還能看懂的話,我還會寫出來。。。。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容