Vue 實例掛載的實現(六)


Vue 中我們是通過 $mount 實例方法去掛載 vm 的,$mount 方法在多個文件中都有定義,如 src/platform/web/entry-runtime-with-compiler.jssrc/platform/web/runtime/index.jssrc/platform/weex/runtime/index.js。因為 $mount 這個方法的實現是和平台、構建方式都相關的。接下來我們重點分析帶 compiler 版本的 $mount 實現,因為拋開 webpack 的 vue-loader,我們在純前端瀏覽器環境分析 Vue 的工作原理,有助於我們對原理理解的深入。

compiler 版本的 $mount 實現非常有意思,先來看一下 src/platform/web/entry-runtime-with-compiler.js 文件中定義:

const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ 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 方法,再重新定義該方法,我們先來分析這段代碼。首先,它對 el 做了限制,Vue 不能掛載在 bodyhtml 這樣的根節點上。接下來的是很關鍵的邏輯 —— 如果沒有定義 render 方法,則會把 el 或者 template 字符串轉換成 render 方法。這里我們要牢記,在 Vue 2.0 版本中,所有 Vue 的組件的渲染最終都需要 render 方法,無論我們是用單文件 .vue 方式開發組件,還是寫了 el 或者 template 屬性,最終都會轉換成 render 方法,那么這個過程是 Vue 的一個“在線編譯”的過程,它是調用 compileToFunctions 方法實現的,編譯過程我們之后會介紹。最后,調用原先原型上的 $mount 方法掛載。

原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定義,之所以這么設計完全是為了復用,因為它是可以被 runtime only 版本的 Vue 直接使用的。

// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } 

$mount 方法支持傳入 2 個參數,第一個是 el,它表示掛載的元素,可以是字符串,也可以是 DOM 對象,如果是字符串在瀏覽器環境下會調用 query 方法轉換成 DOM 對象的。第二個參數是和服務端渲染相關,在瀏覽器環境下我們不需要傳第二個參數。

$mount 方法實際上會去調用 mountComponent 方法,這個方法定義在 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 if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ 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._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm } 

從上面的代碼可以看到,mountComponent 核心就是先實例化一個渲染Watcher,在它的回調函數中會調用 updateComponent 方法,在此方法中調用 vm._render 方法先生成虛擬 Node,最終調用 vm._update 更新 DOM。

Watcher 在這里起到兩個作用,一個是初始化的時候會執行回調函數,另一個是當 vm 實例中的監測的數據發生變化的時候執行回調函數,這塊兒我們會在之后的章節中介紹。

函數最后判斷為根節點的時候設置 vm._isMounted 為 true, 表示這個實例已經掛載了,同時執行 mounted 鈎子函數。 這里注意 vm.$vnode 表示 Vue 實例的父虛擬 Node,所以它為 Null 則表示當前是根 Vue 的實例。

總結

mountComponent 方法的邏輯也是非常清晰的,它會完成整個渲染工作,接下來我們要重點分析其中的細節,也就是最核心的 2 個方法:vm._render 和 vm._update

 

 

-----------------轉自慕課網vue源碼解析視頻教程的內容-----------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM