Vue渲染過程淺析
Vue 推薦在絕大多數情況下使用 template 來建立你的 HTML。但是模板畢竟是模板,不是真實的dom節點。從模板到真實dom節點還需要經過一些步驟
- 把模板編譯為render函式
- 例項進行掛載, 根據根節點render函式的呼叫,遞迴的生成虛擬dom
- 對比虛擬dom,渲染到真實dom
- 元件內部data發生變化,元件和子元件引用data作為props重新呼叫render函式,生成虛擬dom, 返回到步驟3
第一步: 模板到render
在我們使用Vue的元件化進行開發應用的時候, 如果仔細的檢視我們要引入的元件, 例子如下
// App.vue <template> <div> hello word </div> </template> <script> export default { } </script> <style> </style>
在我們的主入口main.js
import Vue from 'vue' import App from './App' console.log(App) new Vue({ render: h => h(App) }).$mount('#app')
我們能夠看到在我們引入的App這個模組,裡面是一個物件,物件裡面存在一個方法叫做render。在說render函式之前,我們可以想一想,每一次載入一個元件,然後對模板進行解析,解析完後,生成Dom,掛載到頁面上。這樣會導致效率很低效。而使用Vue-cli進行元件化開發,在我們引入元件的後,其實會有一個解析器( vue-loader
)對此模板進行了解析,生成了render函式。當然,如果沒有通過解析器解析為render函式,也沒有關係,在元件第一次掛載的時候,Vue會自己進行解析。原始碼請參考: https://github.com/vuejs/vue/...
這樣,能保證元件每次呼叫的都是render函式,使用render函式生成VNode。
第二步:虛擬節點VNode
我們把Vue的例項掛載到 #app
, 會呼叫例項裡面的render方法,生成虛擬DOM。來看看什麼是虛擬節點,把例子修改一下。
new Vue({ render: h => { let root = h(App) console.log('root:', root) return root } }).$mount('#app')
上面生成的VNode就是虛擬節點,虛擬節點裡面有一個屬性 elm
, 這個屬性指向真實的DOM節點。因為VNode指向了真實的DOM節點,那麼虛擬節點經過對比後,生成的DOM節點就可以直接進行替換。
一個元件物件,如果內部的 data
發生變化,觸發了render函式,重新生成了VNode節點。那麼就可以直接找到所對應的節點,然後直接替換。那麼這個過程只會在本元件內發生,不會影響其他的元件。於是元件與元件是隔離的。
例子如下:
// main.js const root = new Vue({ data: { state: true }, mounted() { setTimeout(() => { console.log(this) this.state = false }, 1000) }, render: function(h) { const { state } = this // state 變化重新觸發render let root = h(App) console.log('root:', root) return root } }).$mount('#app')
// App.vue <script> export default { render: (h) => { let app = h('h1', ['hello world']) console.log('app:', app) return app } } </script>
我們可以看到,當 main.js
中重新觸發render函式的時候,render方法裡面有引用App.vue這個子元件。但是並沒有觸發App.vue元件的的render函式。
在一個元件內,什麼情況會觸發render?。
如何才能觸發元件的render
資料劫持是Vue的一大特色,原理官方已經講的很多了 深入響應式原理 。在我們給元件的data的屬性進行的賦值的時候(set),此屬性如果在元件內部初次渲染過程被引用( data的屬性被訪問,也就是資料劫持的get
), 包括生命週期方法或者render方法。於是會觸發元件的update(beforeUpdate -> render -> updated)。
注: 為了防止data被多次set從而觸發多次update, Vue把update存放到非同步佇列中。這樣就能保證多次data的set只會觸發一次update。
當props會觸發元件的重新渲染是怎麼發生的呢?
把父元件的data通過props傳遞給子元件的時候,子元件在初次渲染的時候生命週期或者render方法,有呼叫data相關的props的屬性, 這樣子元件也被新增到父元件的data的相關屬性依賴中,這樣父元件的data在set的時候,就相當於觸發自身和子元件的update。
例子如下:
// main.vue import Vue from 'vue' import App from './App' const root = new Vue({ data: { state: false }, mounted() { setTimeout(() => { this.state = true }, 1000) }, render: function(h) { const { state } = this // state 變化重新觸發render let root = h(App, { props: { status: state } }) console.log('root:', root) return root } }).$mount('#app') window.root = root
// App.vue <script> export default { props: { status: Boolean }, render: function (h){ const { status } = this let app = h('h1', ['hello world']) console.log('app:', app) return app } } </script>
截圖如下:
在 main.js
中 state 狀態發生了變化,由 false
=> true
, 觸發了 自身 與 子元件 的render方法。
補充
上面的內容是本人的一些使用心得,由於水平有限, 內容有些錯誤或者表達不當。多歡迎大神來指導!!!