Vue支持動態style和class,如:<div :class="{active: false}" :style="{color: "red"}"></div>
,如何將樣式屬性映射至真實DOM?vue
咱們的編碼目標是下面的demo可以成功渲染。node
let v = new Vue({
el: '#app',
data () {
return {
color: "red"
}
},
render (h) {
return h('h1', {style: {color: this.color}}, 'hello world!')
}
})
setTimeout(() => {
v.color ='#000'
}, 2000)
複製代碼
【Ts重構Vue】01-如何建立虛擬節點中分析了虛擬DOM映射過程,咱們知道虛擬DOM轉爲真實DOM只有惟一的途徑---patch(oldVnode, vnode)
,是否能夠在這一步上進行一些操做,將樣式更新到真實DOM上?react
觀察下圖,完整patch過程主要涉及以下方法:web
咱們在方法內執行鉤子函數,並將虛擬節點做爲參數傳入,那麼就能夠在節點的生命週期(建立、更新、銷燬)進行各類操做,Vue支持以下鉤子函數:create、destroy、insert、remove、update、prepatch、postpatch、init
。bash
回顧問題,咱們須要設置和更新真實DOM的樣式,因此須要用到create
和update
這兩個鉤子函數:app
createElm
中建立真實DOM,建立完成後調用create-hook
爲節點設置樣式屬性。同時進行賦值操做vnode.elm = 真實節點
,方便後續管理。patchVnode
中對相同虛擬節點進行比較,執行update-hook
對節點的樣式進行更新。鉤子函數是如何存儲和執行的呢?框架
首先聲明變量cbs = {}
用於存儲全局的鉤子函數。異步
let cbs = {} as ModuleHooks
export function createPatcher(modules?: Array<Partial<Module>>) {
modules = isArray(modules) ? modules : []
for (let i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (let j = 0; j < modules.length; ++j) {
const hook = modules[j][hooks[i]]
if (isDef(hook)) {
;(cbs[hooks[i]] as Array<any>).push(hook)
}
}
}
return patch
}
複製代碼
接着調用createPatcher
方法,並傳入styleModule
,返回patch方法用做節點映射。函數
import styleModule from './style'
const patch = createPatcher(styleModule)
// styleModule的形式以下。
styleModule = {
create: updateStyle,
update: updateStyle
}
複製代碼
上面模塊的全局變量存儲了鉤子函數,接着在具體方法中執行。post
在createElm
函數中,當建立完成真實DOM後,執行invokeCreateHook(vnode)
鉤子函數:
function createElm(vnode: VNode): Node {
if (!isVNode(vnode)) return null
if (createComponent(vnode)) {
return vnode.elm
}
if (vnode.tag === '!') {
vnode.elm = webMethods.createComment(vnode.text!)
} else if (!vnode.tag) {
vnode.elm = webMethods.createText(vnode.text!)
} else {
vnode.elm = webMethods.createElement(vnode.tag!)
//hook-create
invokeCreateHook(vnode)
}
return vnode.elm
}
function invokeCreateHook(vnode: VNode) {
invokeCbHooks('create')(emptyNode, vnode)
let i: any = vnode.data.hook
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
複製代碼
在patchVnode
方法中,當新舊虛擬節點不一樣時,執行invokeCbHooks('update')(oldVnode, vnode)
鉤子函數。
function patchNode(oldVnode: VNode, vnode: VNode) {
let i: any
const data = vnode.data,
oldCh = oldVnode.children,
ch = vnode.children,
elm = (vnode.elm = oldVnode.elm!)
vnode.componentInstance = oldVnode.componentInstance
if (oldVnode === vnode) return
invokeVnodeHooks(oldVnode, vnode, 'prepatch')
invokeCbHooks('update')(oldVnode, vnode)
if (oldCh) {
...
} else {
...
}
invokeVnodeHooks(oldVnode, vnode, 'postpatch')
}
function invokeCbHooks(hook: keyof Module) {
let hookHandler = cbs[hook]
return function(...args) {
for (let i = 0; i < hookHandler.length; ++i) {
hookHandler[i](...args)
}
}
}
複製代碼
在虛擬節點映射爲真實DOM過程當中,執行了create
和update
鉤子函數,其本質上市執行了updateStyle
函數,
在updateStyle函數中,從vnode.elm屬性上獲取真實DOM對象,遍歷新舊虛擬節點的樣式屬性,對真實DOM進行設置更新操做。
function updateStyle(oldVnode: VNode, vnode: VNode): void {
let cur: any,
name: string,
elm = vnode.elm,
oldStyle = oldVnode.data!.style,
style = vnode.data!.style
if (!oldStyle && !style) return
if (oldStyle === style) return
oldStyle = oldStyle || ({} as VNodeStyle)
style = style || ({} as VNodeStyle)
for (name in oldStyle) {
if (!style[name]) {
;(elm as any).style[name] = ''
}
}
for (name in style) {
;(elm as any).style[name] = style[name]
}
}
export default {
create: updateStyle,
update: updateStyle
}
複製代碼
在Vue實例化後,生成以下虛擬DOM,並渲染至頁面:
{
tag: 'h1',
ele: 真實DOM節點,
data: {
style: {
color: 'red'
}
},
children: [
{
tag: '',
text: 'hello world'
}
]
}
複製代碼
當用戶更新this.color = '#000'
後,觸發watch.update
函數,從而執行this._update(this._render())
方法,生成新的虛擬DOM:
{
tag: 'h1',
ele: 真實DOM節點,
data: {
style: {
color: '#000'
}
},
children: [
{
tag: '',
text: 'hello world'
}
]
}
複製代碼
在虛擬DOM進行patch過程當中,經過鉤子函數調用了updateStyle方法,函數執行時更新了真實DOM的樣式屬性。
核心模塊僅關心自身邏輯,其餘需求借助鉤子函數實現。既方便不一樣平臺柴藝華,又方便實現拓展。Vue的style/class等功能都是基於vnode-hook實現的。
vue/react都有生命週期,vnode映射過程有鉤子函數,如何開發可拓展的框架?