【Ts重構Vue】03-如何給真實DOM設置樣式

如何給真實DOM設置樣式?

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)

複製代碼

樣式如何映射至真實DOM

【Ts重構Vue】01-如何建立虛擬節點中分析了虛擬DOM映射過程,咱們知道虛擬DOM轉爲真實DOM只有惟一的途徑---patch(oldVnode, vnode),是否能夠在這一步上進行一些操做,將樣式更新到真實DOM上?react

觀察下圖,完整patch過程主要涉及以下方法:web

咱們在方法內執行鉤子函數,並將虛擬節點做爲參數傳入,那麼就能夠在節點的生命週期(建立、更新、銷燬)進行各類操做,Vue支持以下鉤子函數:create、destroy、insert、remove、update、prepatch、postpatch、initbash

回顧問題,咱們須要設置和更新真實DOM的樣式,因此須要用到createupdate這兩個鉤子函數:app

  1. createElm中建立真實DOM,建立完成後調用create-hook爲節點設置樣式屬性。同時進行賦值操做vnode.elm = 真實節點,方便後續管理。
  2. patchVnode中對相同虛擬節點進行比較,執行update-hook對節點的樣式進行更新。
  3. 在節點銷燬時,樣式屬性天然不會展現,此時不須要進行任何操做。

鉤子函數是如何執行的?

鉤子函數是如何存儲和執行的呢?框架

首先聲明變量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過程當中,執行了createupdate鉤子函數,其本質上市執行了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樣式處理流程

在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映射過程有鉤子函數,如何開發可拓展的框架?

系列文章

【Ts重構Vue】00-Ts重構Vue前言

【Ts重構Vue】01-如何建立虛擬節點

【Ts重構Vue】02-數據如何驅動視圖變化

【Ts重構Vue】03-如何給真實DOM設置樣式

【Ts重構Vue】04-異步渲染

【Ts重構Vue】05-實現computed和watch功能

相關文章
相關標籤/搜索