關于 Vue Mixin 和 Directive 的使用

aekf8752 7年前發布 | 21K 次閱讀 Vue.js Vue.js開發

使用 vue 組件化開發已經很長時間,組件化的意義就是把在多個頁面中共有的邏輯(顯示/功能)封裝,在多個頁面或者多個項目中重復使用以提高開發效率。

但組件中共有的邏輯如何重用呢?例如: 我們再開發一個 popover 組件時都會有如下功能:

  1. 點擊外部時關閉彈出
  2. 滾動條滾動或者窗口大小發生改變時重新定位

這里我們需要監聽三個事件,一個是外部被點擊事件,滾動條滾動,窗口大小發生改變,這些功能都很可能在別的組件中被使用,我們要將這些共有功能封裝起來。

Vue 中提供了 mixin (混合) 和 directive 兩種方式用于封裝多個組件內的共有功能。

Mixin(混合)

混合是一種靈活的分布式復用 Vue 組件的方式。混合對象可以包含任意組件選項。以組件使用混合對象時,所有混合對象的選項將被混入該組件本身的選項。

當組件和混合對象含有同名選項時,這些選項將以恰當的方式混合。比如,同名鉤子函數將混合為一個數組,因此都將被調用。另外,混合對象的 鉤子將在組件自身鉤子 之前 調用 :

值為對象的選項,例如 methods, components 和 directives,將被混合為同一個對象。 兩個對象鍵名沖突時,取組件對象的鍵值對。

popover 監聽滾動和window大小變化功能,可以使用 mixin 完成

// scroll.js
export default {
  props: {
    scroller: {
      type: [HTMLDocument, Element, Window],
      default () {
        return window
      }
    }
  },
  mounted () {
    this.$bindScroll()
  },
  methods: {
    $bindScroll () {
      if (!this.scroller) return
      this._handleScroll = (e) => {
        if (this.onScroll) this.onScroll()
      }
      this.scroller.addEventListener('scroll', this._handleScroll)
    },
    $unbindScroll (scroller) {
      scroller = scroller || this.scroller
      if (this._handleScroll) scroller.removeEventListener('scroll', this._handleScroll)
    }
  },
  beforeDestroy () {
    this.$unbindScroll()
  },
  watch: {
    scroller (scroller, oldScroller) {
      if (scroller === oldScroller) return
      this.$unbindScroll(oldScroller)
      this.$bindScroll(scroller)
    }
  }
}
// resize.js
export default {
  mounted () {
    this.$bindResize()
  },
  methods: {
    $bindResize () {
      this._handleResize = (e) => {
        if (this.onResize) this.onResize()
      }
      window.addEventListener('resize', this._handleResize)
    },
    $unBindResize () {
      if (this._handleResize) window.removeEventListener('resize', this._handleResize)
    }
  },
  beforeDestroy () {
    this.$unBindResize()
  }
}

使用時,引入實現對應的方法即可

// popover.js
import Scroll from 'scroll'
import Resize from 'resize'

export default {
    mixins: [Scroll, Resize],
    methods: {
        onScroll () {
            // 滾動時處理
        },
        onResize () {
            // 窗口大小變化時處理
        }
    }
}

同樣 scroll 和 resize 也可以再其它組件中使用,復雜的項目中可以使用 mixin 將組件內共有的功能封裝,但是要注意保證每個 mixin 處理的功能盡量單一,方便組件內部根據需要集成相應的功能。

Directive(指令)

在 Vue2.0 里面,代碼復用的主要形式和抽象是組件——然而,有的情況下,你仍然需要對純 DOM 元素進行底層操作,這時候就會用到自定義指令。

例如: 需要讓輸入框根據參數自動 focus 和 blur

// focus.js
export default {
  bind (el, binding) {
    if (binding.value) el.focus()
  },
  update (el, binding) {
    if (!binding.oldValue && binding.value) el.focus()
  }
}
<template>
    <inputv-focus="isFocus"/>
</template>
<script>
import focus from 'focus'
export default {
  data () {
    return {
      isFocus: false
    }
  },
  directives: {
    focus
  }
}
</script>

指令中有很多鉤子函數,可以根據實際情況實現

  • bind : 只調用一次,指令第一次綁定到元素時調用,用這個鉤子函數可以定義一個在綁定時執行一次的初始化動作。
  • inserted : 被綁定元素插入父節點時調用(父節點存在即可調用,不必存在于 document 中)。
  • update : 被綁定元素所在的模板更新時調用,而不論綁定值是否變化。通過比較更新前后的綁定值,可以忽略不必要的模板更新(詳細的鉤子函數參數見下)。
  • componentUpdated : 被綁定元素所在模板完成一次更新周期時調用。
  • unbind : 只調用一次, 指令與元素解綁時調用。

每個鉤子函數都會有如下參數:

  • el : 指令所綁定的元素,可以用來直接操作 DOM 。
    • binding : 一個對象,包含以下屬性:
    • name : 指令名,不包括 v- 前綴。
    • value : 指令的綁定值, 例如: v-my-directive=”1 + 1”, value 的值是 2。
    • oldValue : 指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
    • expression : 綁定值的字符串形式。 例如 v-my-directive=”1 + 1” , expression 的值是 “1 + 1”。
    • arg : 傳給指令的參數。例如 v-my-directive:foo, arg 的值是 “foo”。
    • modifiers : 一個包含修飾符的對象。 例如: v-my-directive.foo.bar, 修飾符對象 modifiers 的值是 { foo: true, bar: true }。
  • vnode : Vue 編譯生成的虛擬節點,查閱 VNode API 了解更多詳情。
  • oldVnode : 上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。

一般在需要 DOM 操作時我們都需要使用自定義指令的方式去實現,當然一些特殊的事件監聽也可以使用指令,例如上述中的監聽外部點擊事件:

/**
 * element https://github.com/ElemeFE/element
 * clickoutside.js
 */
const clickoutsideContext = '@@clickoutsideContext'

export default {
  bind (el, binding, vnode) {
    const documentHandler = function(e){
      if (!vnode.context || el.contains(e.target)) return
      if (binding.expression) {
        vnode.context[el[clickoutsideContext].methodName](e)
      } else {
        el[clickoutsideContext].bindingFn(e)
      }
    }
    el[clickoutsideContext] = {
      documentHandler,
      methodName: binding.expression,
      bindingFn: binding.value
    }
    setTimeout(()=> {
      document.addEventListener('click', documentHandler)
    }, 0)
  },

  update (el, binding) {
    el[clickoutsideContext].methodName = binding.expression
    el[clickoutsideContext].bindingFn = binding.value
  },

  unbind (el) {
    document.removeEventListener('click', el[clickoutsideContext].documentHandler)
  }
}
<template>
<divv-clickoutside="handleClickOutSide"></div>
</template>
<script>
import clickoutside from 'clickoutside'
export default {
  methods: {
    handleClickOutSide () {
        // 當外部被點擊時調用
    }
  },
  directives: {
    clickoutside
  }
}
</script>

雖然 directive 和 mixin 都可用于封裝組件中的公共功能,但是 directive 更趨向于有dom操作時使用,而mixin可以承擔更復雜的功能封裝。

以上代碼,都在 muse-ui 中實踐

 

來自:http://www.myronliu.com/2017/01/31/vue/vue_mixins_directive/

 

 本文由用戶 aekf8752 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!