上一篇博客我和大家分享了vue render函數的基礎使用
這篇博客我們來簡單講一講render函數他是怎么實現得
先來一張官方得圖
在實例初始化得時候,html通過render函數編譯生成了一個虛擬dom,視圖層會根據虛擬dom生成一個真實dom
然后如果響應數據發生變化得時候,render函數會重新調用,在更新得時候render函數會返回一個新的虛擬dom , 這個新的虛擬dom並不會直接把之前得替換掉,他會對比新舊dom,然后通過diff算法,把改動最小得結果,拿去更新真實dom,避免發生大量得dom回流和重繪
render函數觸發的時機
剛才在上邊已經說過了,render函數會在響應數據發生改變的時候去被觸發,那么就不得不說一說vue的響應式原理
之前就聽說過vue實現的原理是數據劫持,那么他到底是怎么做到的數據劫持這件事呢?
在vue的源碼里邊可以看到,vue實例在初始化的時候會把data里邊所有的屬性添加一個對象進去
function observe (obj) {
// 迭代對象的所有屬性
// 並使用Object.defineProperty()轉換成getter/setters
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
// 每個屬性分配一個Dep實例
const dep = new Dep()
Object.defineProperty(obj, key, {
// getter負責注冊訂閱者
get () {
dep.depend()
return internalValue
},
// setter負責通知改變
set (newVal) {
const changed = internalValue !== newVal
internalValue = newVal
// 觸發后重新計算
if (changed) {
dep.notify()
}
}
})
})
return obj
}
在沒有更改屬性的原有行為的基礎上加了一個依賴對象進去,然后通過這個以來對象來發送通知告訴監聽watcher 數據發生了變化 ,然后watcher去調用render函數 更新dom
render函數中的jsx語法
jsx事js內定義的一套類xml語法,可以解析出js代碼,由js引擎解析,在上一篇博客里邊我使用的是render函數本身的寫法,但是感覺十分的冗余,jsx里邊則非常簡介,你可以直接返回一個標簽的所有屬性,感覺和html沒什么兩樣 比如說
render() {
const { count, onChange } = this;
return (
<div>
<componment
style={{ marginTop: "10px" }}
count={count}
type="button"
onChange={onChange}
/>
<componment
style={{ marginTop: "10px" }}
count={count}
type="button"
domPropsInnerHTML={`hello ${this.count}.`}
onChange={onChange}
/>
</div>
);
}
上邊的代碼可以輕松的渲染出來兩個組件
需要注意的是使用jsx語法的時候需要babel插件,官方插件連接
createElement
render函數里邊用的createElement方法創建的vnode,createElement方法在vue的源碼里邊是對_createElement方法的封裝,之前是由五個參數的,其中有一個參數是對子節點的犯規,
我們使用的createElement在經過規范化之后,默認返回的一個vnode類型的數組
vnode有四個屬性
- tag
- data
- context
- children
第一個是節點的名稱 首先會對tag進行一個判斷,如果是字符串的話,會去看是不是html規范的一些檢點,如果是的話會創建一個普通的vnode節點
如果是以惡搞注冊過的組件名稱,則會createComponent 創建一個組件類型的vnode
如果都不是則會創建一個位置的標簽
什么是vnode
所謂虛擬DOM,是一個用於表示真實 DOM 結構和屬性的 JavaScript 對象,這個對象用於對比虛擬 DOM 和當前真實 DOM 的差異化,然后進行局部渲染從而實現性能上的優化。
vnode的渲染
在render函數生成完畢虛擬dom樹之后 就開始了vnode的首次渲染,vue里邊調用的是_update方法
// src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
// 初始化渲染
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
} else {
// 更新渲染
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
可以看出不管如何最終都是調用的__patch__方法
這個__patch__方法主要有兩個作用 第一個是創建真實dom 另外一個是發生變化之后把新老的虛擬dom進行對比然后更新真實dom
這個方法對應的方法是createPatchFunction() 有興趣的可以去源碼里邊搜一下
結束語
以上是我對render函數的從響應到渲染還有他的作用的理解 有不對的地方歡迎批評指正