您好,登錄后才能下訂單哦!
使用vue進行渲染的過程有哪些?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
Vue是一款友好的、多用途且高性能的JavaScript框架,使用vue可以創(chuàng)建可維護性和可測試性更強的代碼庫,Vue允許可以將一個網(wǎng)頁分割成可復(fù)用的組件,每個組件都包含屬于自己的HTML、CSS、JavaScript,以用來渲染網(wǎng)頁中相應(yīng)的地方,所以越來越多的前端開發(fā)者使用vue。
首先,我們看main.js中,第一個最關(guān)鍵的肯定是引入vue吧
import vue from 'vue'
其實,vue被打包后,dist文件夾中存在多個版本,分別是
通用版本(UMD):中的完整版 vue.js 和運行時版本 vue.runtime.js
CommonJs版本:中的完整版vue.common.js 和 運行時版本vue.runtime.common.js
ES Module版本:中的完整版vue.esm.js 和 運行時版本vue.runtime.esm.js
一般在vue2.6以后,我們用vue/cli創(chuàng)建的項目用的都是vue.runtime.esm.js運行時版本
即,引入vue時會引入vue.esm.js這個版本
那么,vue引入以后,是不是vue中的相關(guān)代碼會被執(zhí)行啊。那最新執(zhí)行vue源碼中的哪塊代碼呢(引入的vue就是vue源碼中被打包后的vue),我們先得知道入口文件在哪
vue的入口文件主要在vue源碼結(jié)構(gòu)的src/platforms/web下
vue打包時,可以選擇不同的vue入口文件來進行打包,不同的入口文件打包出來的vue版本不同。
這里我們主要來說完整版entry-runtime-with-compiler.js
下面我們先來了解下完整版和運行時版本的區(qū)別
完整版是運行時版本 + 編譯器的組合
運行時版本不帶編譯器compiler,即沒有模板編譯功能,主要用來創(chuàng)建vue實例,渲染虛擬dom。體積小,更輕量(compiler編譯器有3000多行代碼)
什么意思呢,即
<body> <div id="app"> <p>我是index.html中的內(nèi)容</p> </div> </body>
new Vue({ template: '<div>我是template模板渲染出來的內(nèi)容</div>' }).$mount('#app')
上面的情況,
如果是完整版vue,存在compiler編譯器,會將new Vue時傳入的template編譯成render函數(shù),并賦值給options的render屬性,然后$mount后,會渲染render函數(shù)成虛擬dom,再將虛擬dom轉(zhuǎn)話為真實dom,所以最終頁面會出現(xiàn) 我是template模板渲染出來的內(nèi)容 這句話。原本的那句話會被覆蓋
如果是運行時版本,沒有編譯器,不會編譯template中的內(nèi)容,則頁面只會存在原來的dom
下面我們來繼續(xù)往下看
找到入口文件后,我們開始看看會執(zhí)行哪些東西
可以看出,入口文件先導(dǎo)入了vue,然后經(jīng)過了一些處理,最終又導(dǎo)出了vue
我們先通過導(dǎo)入vue的路徑一步一步找到vue構(gòu)造函數(shù)的創(chuàng)建在哪創(chuàng)建的。如上圖,從runtime/index中導(dǎo)入了vue,那么我們?nèi)タ磖untime/index
這個文件也是一樣,import了vue 經(jīng)過了一些處理,然后又導(dǎo)出了vue,我們繼續(xù)往上找,找core/index
這個文件也是一樣,我們繼續(xù)往上找,找./instance/index
在這里,我們找到了我們的vue構(gòu)造函數(shù)的創(chuàng)建,是在源碼的src/core/instance/index.js文件中。
那么,我們從剛剛上面的引用關(guān)系,就能發(fā)現(xiàn),vue被我們引入到項目中后,首先會執(zhí)行的文件的順序是
src/core/instace/index.js ===> 1 src/core/index.js ===> 2 src/platforms/web/runtime/index.js ===> 3 src/platforms/web/entry-runtime-with-compiler.js 4
那么,我們再來看,每個文件都執(zhí)行了些什么,
首先 src/core/instace/index.js
首先,此文件定義了vue構(gòu)造函數(shù),并初始化了一些vue的實例屬性和實例方法,即,在vue.prototype原型下新增了各種方法和屬性
下面,我們具體來看下,每一個方法具體初始化了vue的哪些實例屬性或方法
1.1.1、initMixin(Vue)
1.1.2、stateMixin(Vue)
1.1.3、eventsMixin(Vue)
1.1.4、lifecycleMixin(Vue)
1.1.5、renderMixin(Vue)
src/core/instace/index.js執(zhí)行完后,會繼續(xù)執(zhí)行下一個文件
export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // 新增了一個config屬性 Object.defineProperty(Vue, 'config', configDef) // 新增了一個靜態(tài)成員 util Vue.util = { warn, extend, mergeOptions, defineReactive } // 新增了3個靜態(tài)成員set delete nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 新增了一個靜態(tài)成員 observable Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 初始化了options 此時options是空對象</T> Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue // 注冊了一個全局組件keep-alive builtInComponents內(nèi)部就是keep-alive的組件導(dǎo)出 extend(Vue.options.components, builtInComponents) // 下面是分別初始化了Vue.use() Vue.mixin() Vue.extend() initUse(Vue) initMixin(Vue) initExtend(Vue) // 初始化Vue.directive(), Vue.component(), vue.filter() initAssetRegisters(Vue) }
可以看出,這個文件,主要是給vue新增了很多靜態(tài)實例方法和屬性,具體新增了哪些,
我們繼續(xù)看被執(zhí)行的那個方法initGlobalAPI(Vue)
1.2.1 initGlobalAPI(Vue)
export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // 新增了一個config屬性 Object.defineProperty(Vue, 'config', configDef) // 新增了一個靜態(tài)成員 util Vue.util = { warn, extend, mergeOptions, defineReactive } // 新增了3個靜態(tài)成員set delete nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 新增了一個靜態(tài)成員 observable Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 初始化了options 此時options是空對象</T> Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue // 注冊了一個全局組件keep-alive builtInComponents內(nèi)部就是keep-alive的組件導(dǎo)出 extend(Vue.options.components, builtInComponents) // 下面是分別初始化了Vue.use() Vue.mixin() Vue.extend() initUse(Vue) initMixin(Vue) initExtend(Vue) // 初始化Vue.directive(), Vue.component(), vue.filter() initAssetRegisters(Vue) }
此文件,最主要的作用就重寫了vue原型下的$mount方法。具體$mount方法中做了些什么,我們后面會講
上面寫的整個過程,都是用戶在使用vue時,引入vue文件后,立刻會執(zhí)行的一些東西
這些執(zhí)行完后,是不是會繼續(xù)去執(zhí)行我們項目中的main.js文件啊,
此時會執(zhí)行到
new Vue({ router, store, render: h => h(App) }).$mount('#app')
這個時候,會開始調(diào)用,我們的vue構(gòu)造函數(shù)
此時,會先執(zhí)行vue構(gòu)造函數(shù),
可以看出,主要是執(zhí)行了_init方法,從這里開始,vue的生命周期開始執(zhí)行了
上面只是_init()方法中最主要的一部分代碼,(代碼太多,我就不全部截圖了,你們自己到源碼中看)??梢钥闯觯?/p>
在生命周期beforeCreate鉤子之前,vue主要做的事情就是給vue原型新增各種屬性和方法,給vue新增各種靜態(tài)屬性和方法,以及給vm實例新增各種屬性和方法
上圖可以看出,beforeCreate鉤子執(zhí)行結(jié)束后,主要執(zhí)行了3個方法:initInjections, initState, initProvide
// 把inject注入到vm實例 callHook(vm, 'beforeCreate') // 把inject注入到vm實例 initInjections(vm) // 初始化vm的$props,$methods,$data,computed,watch initState(vm) // 把provide注入vm實例 initProvide(vm) // 執(zhí)行created生命周期 callHook(vm, 'created')
其實,重點是initState(vm)方法,該方法中,初始化了vm實例的$props, $data, $methods, computed, watch等。同時,在里面調(diào)用了一個initData()方法,該方法內(nèi)會調(diào)用observer() 方法,將data中的數(shù)據(jù)都轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)。即添加數(shù)據(jù)攔截器。
所以可以看出,在created生命周期之前,vm的$props, $data, $methods, computed, watch屬性都會初始化完成,
故,這也就是為什么,我們可以在created中調(diào)用我們data中的各種數(shù)據(jù)以及調(diào)用props或者methods等下面的各種方法了。
created生命周期走完以后,繼續(xù)往下看
可以看出,這里判斷了vm.$options.el是否存在,vm.$options.el是什么啊。
new Vue({})時,傳入的那個對象的所有屬性,都會被掛載options下,
new Vue({ el: '#app' router, store, render: h => h(App) })
故,vm.$options.el就是上面?zhèn)魅氲膃l。
這里判斷el是否存在,如果存在,才會繼續(xù)往下執(zhí)行$mount
那大家可能會好奇了,如果不存在,那是不是就卡死了,后面都不會走了。是的,如果沒有,就不會繼續(xù)走了,要想代碼繼續(xù)往下走,必然要執(zhí)行$mount方法。
此時,我們再看一直vue常用的情況
new Vue({ router, store, render: h => h(App) }).$mount('#app')
這里沒有傳入el,所以源碼中的
肯定是不會走的。但是,用戶在new Vue的時候可以自己用new 出來的vue實例去調(diào)用$mount。這么一來,大家看我們官網(wǎng)的生命周期圖,可能就更容易看懂了
好了,下面我們繼續(xù)往下,下一步是執(zhí)行 $mount,我們來看 $mount方法
我們之前初始化的時候,重寫過$mount還記得嗎,所以,此時我們執(zhí)行$mount時,執(zhí)行的是重寫后的mount
這里在重寫前先存儲了重寫前的mount方法,然后在最后調(diào)用了重寫前的mount方法。
重寫后,最關(guān)鍵的代碼是判斷是否有render函數(shù)
這一步的主要作用就是判斷是否有render函數(shù),
如果有,直接往下執(zhí)行重寫前的$mount方法前渲染render函數(shù),
如果沒有,就會前判斷是否存在template模板(options.template是否存在,options.template可能是id選擇器,可能是dom),如果存在模板,就會獲取到模板中的內(nèi)容,并賦值給template,options.template不存在,那么會直接以el指定的dom為模板(即#app),獲取到el下的dom,賦值給template
template取到dom后,然后繼續(xù)往下,將此template編譯成render函數(shù),并將編譯出來的render函數(shù)掛載options.render屬性下
然后會繼續(xù)執(zhí)行重寫前的$mount,理解了這,我們就能理解生命周期圖中的另一部分了
下面,我們繼續(xù)來看重寫前的$mount函數(shù)的執(zhí)行
可以看出\ $mount中主要是執(zhí)行了函數(shù)mountComponent,我們繼續(xù)看mountComponent函數(shù)
可以看出,此函數(shù),主要做了以下4件事
我們一件一件來看
1、執(zhí)行了beforeMount鉤子,所以可以得出結(jié)論,再beforeMount之前,我們主要是初始化和得到render函數(shù)。而beforeMount之后,才是開始將render函數(shù)渲染成虛擬dom,然后更新真實dom
render函數(shù)得到的途徑有3種
第一:用戶自己傳入render
第二:.vue文件編譯成render
這種方式,就是自己傳入了一個render函數(shù),函數(shù)內(nèi)用h函數(shù)前執(zhí)行了App.vue文件。
.vue文件最終轉(zhuǎn)化為render函數(shù)需要借助vue-loader來完成
第三、將template模板編譯成render函數(shù)
2、定義了一個updateComponent函數(shù),此函數(shù)內(nèi)調(diào)用了vm的_update方法,同時執(zhí)行了vm._render()方法,并將執(zhí)
行后的結(jié)果當(dāng)做參數(shù)傳給_update方法。_render方法我們前面說過,他內(nèi)部渲染了render函數(shù)成為虛擬dom,故_render()的返回值是一個vnode。
我們先來看下_render()函數(shù)內(nèi)部如何將render函數(shù)轉(zhuǎn)化為虛擬dom的
然后我們再看_update函數(shù)內(nèi)部做了啥
可以看出,_update函數(shù)中,執(zhí)行了__patch__方法去對比兩個新舊dom,從而找出差異,更新真實dom。如果是首次渲染,則直接將當(dāng)前的vnode,生成真實的dom。
故得出結(jié)論,整個updateComponent方法的主要作用就是渲染render函數(shù),更新dom
而什么時候更新dom的關(guān)鍵,就在于什么時候去調(diào)用這個updateComponent函數(shù)了
3、new 了一個watcher實例
可以看出,new一個watcher實例的同時,傳入了updateComponent函數(shù)作為參數(shù)。
此時,我們看new Watcher時,會執(zhí)行Watcher構(gòu)造函數(shù),我們看Watcher構(gòu)造函數(shù)內(nèi)做了啥
watcher分為3種,渲染watcher,$watch函數(shù)的watcher,computed的watcher。我們這里渲染頁面的是渲染watcher
上面將我們傳入的函數(shù)傳給了getter
繼續(xù)往下走,調(diào)用了get()
可以看出,get()中調(diào)用了我們傳入的函數(shù),而我們傳入的函數(shù)就是渲染render函數(shù),并觸發(fā)虛擬dom更新真實dom,而返回的值,就是渲染后的真實dom,最后賦值給了this.value,而this.value最后會用于更新依賴者。而我們當(dāng)前這個wather實例,是主vue實例的watcher,故可以理解為整個頁面的watcher。當(dāng)我們調(diào)用this.$fouceUpdate()時,就是調(diào)用這個實例的update方法,去更新整個頁面。
所以說,new Wacher的時候 updateComponent會自動調(diào)用一次,這就是我們的首次渲染。
此時,我們繼續(xù)往下看
這內(nèi)部,還做了個判斷,如果vm._isMounted為true(即Mounted鉤子已經(jīng)執(zhí)行過了),而vm._isDestroyed為fase時(即當(dāng)前組件還未銷毀)。此時,如果產(chǎn)生更新,則說明并非首次渲染,那么執(zhí)行beforeUpdate鉤子,后續(xù)肯定還會走updated。這里我們就不說updated的事了
new Watcher后,代碼繼續(xù)往下走
判斷了當(dāng)前vnode如果null,說明之前沒有生成過虛擬dom,也就說明這次肯定是首次渲染,此時,vm._isMounted置為true。并執(zhí)行mounted鉤子函數(shù),此時,首次渲染完成。
可以看出,整個beforeMount 到 mounted過程中,主要做的工作就是
1、渲染render函數(shù)成為虛擬dom vnode
2、執(zhí)行vm._update函數(shù),將虛擬dom轉(zhuǎn)化為真實dom
如果是beforeUpdate 到 updated鉤子之間,說明不是首次渲染,那么虛擬dom會有新舊兩個。此時vm._update函數(shù)的作用就是對比新舊兩個vnode,得出差異,更新需要更新的地方
看完上述內(nèi)容,你們掌握使用vue進行渲染的過程有哪些的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。