如果你需要在客戶端編譯模板 (比如傳入一個字符串給 template 選項,或掛載到一個元素上並以其 DOM 內部的 HTML 作為模板),就將需要加上編譯器,即完整版
當使用 vue-loader 或 vueify 的時候,*.vue 文件內部的模板會在構建時預編譯成 JavaScript。你在最終打好的包里實際上是不需要編譯器的,所以只用運行時版本即可— 官方文檔
客戶端編譯模板
1 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> 2 3 <div id="app"></div> 4 5 <script> 6 new Vue({ 7 el: '#app', 8 data: { 9 message: 'Hello Vue!' 10 }, 11 template: '<div>{{ message }}</div>' 12 }) 13 </script>
這種用法就需要在客戶端(即瀏覽器中)編譯模板,模版的內容是 <div>{{ message }}</div> ,模版的數據是 message: 'Hello Vue!' 。
因此,如果使用 script 引入只有運行時版本的vue.js vue.runtime.js ,就會報錯:
vue.runtime.js:593 [Vue 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.
使用渲染函數
1 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.runtime.js"></script> 2 3 <div id="app"></div> 4 5 <script> 6 new Vue({ 7 el: '#app', 8 data: { 9 message: 'Hello Vue!' 10 }, 11 render (createElement) { 12 return createElement('div', this.message) 13 } 14 }) 15 </script>
這種用法就是直接給出渲染函數來進行內容輸出(具體 createElement 語法后面再講),這種情況下不需要進行客戶端渲染,直接引用運行時版本的vue.js即可,並不會報錯。
預編譯模板
簡單的頁面結構我們可以直接通過 createElement 函數來手動編寫,但是對於復雜的頁面結構呢?vue提供了 Vue.compile 函數用於將模版編譯成 render 函數,示例如下:
1 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> 2 3 <script> 4 const result = Vue.compile('<div>{{ message }}</div>'); 5 console.log(result.render); 6 </script>
注意:只有 Runtime + Compiler 版本的vuejs才有 Vue.compile 函數,運行時版本的vue.js是沒有這個函數的,所以這里也就是所謂的預編譯。
輸出的結果如下:
1 ƒ anonymous( 2 ) { 3 with(this){return _c('div',[_v(_s(message))])} 4 }
我們用預編譯生成的render
函數替代上一章的 render 函數:
1 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.runtime.js"></script> 2 3 <div id="app"></div> 4 5 <script> 6 new Vue({ 7 el: '#app', 8 data: { 9 message: 'Hello Vue!' 10 }, 11 render (createElement) { 12 with(this){return _c('div',[_v(_s(message))])} 13 } 14 }) 15 </script>
可以看到這種“開發時預編譯,上線使用運行時”的方式既滿足了開發需要又減少了線上文件的大小。
注:通過打印,可以看到 createElement 以及 this._c , this._v 和 this._s 的值:
1 render (createElement) { 2 console.log(createElement, this._c, this._v, this._s) 3 with(this){return _c('div',[_v(_s(message))])} 4 } 5 ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, true); } 6 ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); } 7 ƒ createTextVNode (val) {...} 8 ƒ toString (val) {...}
所以可以看出,我們最開始手寫的渲染函數 return createElement('div', this.message) 只是上面預編譯生成的一個簡版。
基於 HTML 的模板語法
除了上面幾種基於js代碼的形式來創建模版,vuejs也支持基於 HTML 的模板語法,用法如下:
1 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> 2 </head> 3 <body> 4 <div id="app"><div>{{ message }}</div></div> 5 6 <script> 7 new Vue({ 8 el: '#app', 9 data: { 10 message: 'Hello Vue!' 11 } 12 }) 13 </script>
注意,這種寫法也必須要使用 Runtime + Compiler 版本的vuejs才可以允許,因為其實質上還是在客戶端進行模版編譯,因為上面的寫法實質上等同於下面的寫法:
1 <div id="app"></div> 2 <template id="tpl"> 3 <div>{{ message }}</div> 4 </template> 5 6 <script> 7 new Vue({ 8 el: '#app', 9 data: { 10 message: 'Hello Vue!' 11 }, 12 template: '#tpl' 13 }) 14 </script>
vuejs源碼中的 template 解析
在vuejs源碼中,關於獲取 template 的關鍵代碼如下:
1 var template = options.template; 2 if (template) { 3 if (typeof template === 'string') { 4 if (template.charAt(0) === '#') { 5 template = idToTemplate(template); 6 } 7 } else if (template.nodeType) { 8 template = template.innerHTML; 9 } else { 10 { 11 warn('invalid template option:' + template, this); 12 } 13 return this 14 } 15 } else if (el) { 16 template = getOuterHTML(el); 17 }
邏輯主要步驟如下:
- 先判斷是否有 template 屬性
- 如果沒有,則直接通過 el 中的 html 代碼作為模版
- 如果有,判斷是否是字符串(非字符串的形式暫不討論)
- 是字符串的情況下,是否以
#
字符開頭 - 如果是,則獲取對應id的 innerHTML 作為模版
- 如果不是以
#
字符開頭,則直接作為作為模版
總結
一句話來講就是:如果有 render 函數就可以使用運行時版本的vuejs,否則必須使用 Runtime + Compiler 版本的vuejs。