petite-vue原始碼剖析-事件繫結`v-on`的工作原理

肥仔John發表於2022-03-16

在書寫petite-vue和Vue最舒服的莫過於通過@click繫結事件,而且在移除元素時框架會幫我們自動解除繫結。省去了過去通過jQuery的累贅。而事件繫結在petite-vue中就是一個指令(directive),和其他指令類似。

深入v-on的工作原理

walk方法在解析模板時會遍歷元素的特性集合el.attributes,當屬性名稱name匹配v-on@時,則將屬性名稱和屬性值壓入deferred佇列的隊尾,噹噹前元素所有屬性繫結和v-modal處理後以及子元素所有屬性繫結、v-modal和事件繫結處理後再處理。

那問題來了,為什麼要將事件繫結放到最後處理呢?

//檔案 ./src/on.ts
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta']

const modifiersGuards: Record<
  string,
  (e: Event, modifiers: Record<string, true>) => void | boolean
> = {
  stop: e => e.stopPropagation(),
  prevent: e => e.preventDefault(),
  self: e => e.target !== e.currentTarget,
  ctrl: e => !(e as KeyedEvent).ctrlKey,
  shift: e => !(e as KeyedEvent).shiftKey,
  alt: e => !(e as KeyedEvent).altKey,
  meta: e => !(e as KeyedEvent).metaKey,
  left: e => 'button' in e && (e as MouseEvent).button !== 0,
  middle: e => 'button' in e && (e as MouseEvent).button !== 1,
  right: e => 'button' in e && (e as MouseEvent).button !== 2,
  /* @click.alt.shift 將分別匹配alt和shift兩個modifiers guards,當此時按alt+shift+ctrl時,兩個modifiers guards均通過。
   * 而@click.alt.shift.exact 將分別匹配alt、shift和exact,當此時按alt+shift+ctrl時,前面兩個modifiers guards均通過,但最後的exact guard將返回true,不執行事件回撥函式。
   */
  exact: (e, modifiers) => 
    systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers[m])
}

export const on: Directive({ el, get, exp, arg, modifiers }) => {
  let handler = simplePathRE.test(exp)
    ? get(`(e => ${exp}(e)`)
    : get(`($event => { ${exp} })`)

  if (arg === 'vue:mounted') {
    // 假如繫結的是生命週期函式mounted,但由於當前元素早已新增到DOM樹上,因此將函式壓入micro queue執行
    nextTick(handler)
    return
  }
  else if (arg === 'vue:unmounted') {
    // 假如繫結的是生命週期函式unmounted,則返回cleanup函式
    return () => handler()
  }

  if (modifiers) {
    // 如果存在modifiers,則對事件繫結進行增強

    if (arg === 'click') {
      // @click.right 對應的DOM事件是contextmenu
      if (modifiers.right) arg = 'contextmenu'
      // @click.middle 對應的DOM事件是mouseup
      if (modifiers.middle) arg = 'mouseup'
    }

    const raw = hanlder
    handler = (e: Event) => {
      if ('key' in e && !(hyphenate((e as KeyboardEvent).key) in modifiers)) {
        /* 如果為鍵盤事件,鍵不在沒有在modifiers中指定則不執行事件回撥函式
         * key值為a、b、CapsLock等,hyphenate將CapsLock轉換為caps-lock
         */ 
        return
      }
      for (const key in modifiers) {
        // 執行modifiers對應的邏輯,若返回true則不執行事件回撥函式
        const guard = modiferGuards[key]
        if (guard && guard(e, modifiers)) {
          return
        }
        return raw(e)
      }
    }
  }

  // 居然沒有返回cleanup函式??大家可以去貢獻程式碼了哈哈
  listen(el, arg, handler, modifers)
}
//檔案 ./src/utils.ts

export const listen = (
  el: Element,
  event: string,
  handler: any,
  opotions?: any
) => {
  el.addEventListener(event, handler, options)
}

總結

現在我們已經瞭解了v-bindv-on的工作原理,後面我們一起看看v-modal吧!

相關文章