科學處理多個 Vue 專案中共用的元件的 Events 和 Vuex

LancerComet發表於2017-01-21

我們在做專案的時候,應該會有這種情況:

“我寫了一個元件,然後做成了 npm 包,然後給好幾個專案一起用。”

Vue 元件也是可以這麼幹的,所以在公司內部可能會將元件封裝成 npm 模組後分發給各個專案。

不過在 Vue 的專案中,有兩個小地方可能需要精心處理下 (●’◡’●)

當公共元件使用 EventBus 時

EventBus 並不是什麼獨立的東西,而是 Vue 的事件系統的一個最佳實踐,算是一種使用方式:

/**
 * EventBus.
 * src/event-bus.js
 */
export default new Vue({})


/**
 * 我的公用元件 my-component.
 */
import EventBus from `src/event-bus`

export default {
  ...
  methods: {
    // 觸發叫做 "SomeModule:SomeEvent" 的事件並傳了值 "Yeah~" 過去~
    triggerSomeEvent () {
      EventBus.$emit(`SomeModule:SomeEvent`, `Yeah~`)
    },

    // 為我的元件註冊兩個事件~
    registerEvents () {
      EventBus.$on(`MyComponent:Event-01`, value => {
        console.log(`Event-01 in MyComponent: `, value)
      }),

      EventBus.$on(`MyComponent:Event-02`, value => {
        console.log(`Event-02 in MyComponent: `, value)
      })
    }
  },

  created () {
    this.registerEvents()
  }
}

當我們的公用模組在使用 EventBus 的時候,會有一個微小的問題,看這句話:

import EventBus from `src/event-bus`

我怎麼保證在使用我當前模組的不同的專案中的 EventBus 的路徑都是 src/event-bus 呢?

所以,我們需要抽象一層,讓模組並不關心這個 EventBus 是從哪裡引入的:

// 我們將 EventBus 做成外掛,這樣就可以在專案的任何元件內使用了.
// 起名叫 $events.
// 當檢測到 $events 存在的時候就使用,不存在的時候使用其他方法.

/**
 * 我們將 event-bus 封裝為一個外掛.
 * plugin/event-bus.js
 */
export default {
  install (Vue) {
    const EventBus = new Vue({})
    Vue.prototype.$events = EventBus
    Vue.EventBus = EventBus
  }
}

/**
 * 所以我的公用元件 my-component 要變為:
 */
export default {
  ...
  methods: {
    // 觸發叫做 "SomeModule:SomeEvent" 的事件並傳了值 "Yeah~" 過去~
    triggerSomeEvent () {
      if (this.$events) {
        this.$events.$emit(`SomeModule:SomeEvent`, `Yeah~`)        
      } else {
        // 其他方式...
      }
    },

    // 為我的元件註冊兩個事件~
    registerEvents () {
      if (this.$events) {
        this.$events.$on(`MyComponent:Event-01`, value => {
          console.log(`Event-01 in MyComponent: `, value)
        }),

        this.$events.$on(`MyComponent:Event-02`, value => {
          console.log(`Event-02 in MyComponent: `, value)
        })
      } else {
        // 其他方式...
      }
    }
  },

  created () {
    this.registerEvents()
  }
}

/**
 * 專案入口.
 * src/index.js
 */
import Vue from `vue`
import EventBus from `plugin/event-bus`
import MyComponent from `my-component`

Vue.use(EventBus)

const Root = new Vue({
  components: {
    MyComponent
  },

  methods: {
    doSomething () {
      this.$events.$emit(`MyComponent:Event-01`, `FA♂`)
    }
  }
})

OK,這樣我們的元件就可以在不同專案中適應 EventBus 了!

這裡有一個元件 cklmercer/vue-events 就是解決這種問題而存在的.

當公共元件使用 Vuex 時

這個問題僅僅存在於 Vue 1.0 的專案中,Vue 2.0 + Vuex 2.0 已經解決這個問題:

/**
 * 我的公用元件 my-component.
 */
import store from `src/vuex/store`
import actions from `src/vuex/actions`
import getters from `src/vuex/getters`

export default {
  ...
  store,

  vuex: {
    actions, getters
  },

  computed: {
    userName () {
      // "getUsername" 是 Vuex 中定義好的 getter.
      return this.getUsername
    }
  },

  methods: {
    changeDataInVuexByUsingAction () {
      // "setUserExperience" 是 Vuex 中定義好的 action.
      this.setUserExperience(450)
    }
  }
}

那麼還是同樣的問題,

我怎麼保證在使用我當前模組的不同的專案中的 Vuex 的路徑都是 src/vuex 呢?

所以方法一樣啦,抽象出來引用路徑,讓模組並不關心是如何引入 Vuex 的:

// 我們將 Vuex 做成外掛,這樣就可以在專案的任何元件內使用了.
// 起名叫 $vuexer.
// 當檢測到 $vuexer 存在的時候就使用 Vuex,不存在的時候就將資料寫入元件自己內部的 state 中.

/**
 * 我們將 event-bus 封裝為一個外掛.
 * plugin/event-bus.js
 */
export default {
  install (Vue, { store, actions, getters }) {
    const vuexer = new Vue({
      store, actions, getters
    })
    Vue.prototype.$vuexer = vuexer
    Vue.vuexer = vuexer
  }
}

/**
 * 專案入口.
 * src/index.js
 */
import Vue from `vue`
import Vuexer from `plugin/vuexer`

import store from `src/vuex/store`
import actions from `src/vuex/actions`
import getters from `src/vuex/getters`

import MyComponent from `my-component`

Vue.use(Vuexer, {
  store, actions, getters
})

const Root = new Vue({
  components: {
    MyComponent
  },

  computed: {
    userExperience () {
      // "getExperience" 是在 Vuex 中定義好的 getter.
      return this.$vuexer.getExperience
    }
  },

  methods: {
    changeUsernameInVuex () {
      // "setUsername" 是在 Vuex 中定義好的 setter.
      this.$vuexer.setUsername(`John Smith`)
    }
  }
})

/**
 * 我的公用元件 my-component.
 */
export default {
  data () {
    return {
      _userName: `神祕使用者`,
      _userExperience: 65535
    }
  },

  computed: {
    userName () {
      // 如果有 Vuexer, 如果木有 Vuexer...
      return this.$vuexer
        ? this.$vuexer.getUsername
        : this._userName
    }
  },

  methods: {
    // 如果有 vuexer, 如果木有 Vuexer...    
    changeDataInVuexByUsingAction () {
      const userExperience = 450
      if (this.$vuexer) {
        this.$vuexer.setUserExperience(userExperience)
      } else {
        this._userExperience = userExperience
      }
    }
  }
}

妥!⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄

至於為什麼 Vue 2.0 + Vuex 2.0 木有這個問題:

// 在 Vue 2.0 中使用 Vuex 要這麼寫:
// 建立一個元件.
const Components = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.getters.doneTodosCount  // 這是一個 getter.
    }
  }
}

注意 computed 中的 return this.$store.getters.doneTodosCount,看看其中的 this.$store

是不是和 this.$vuexer 有點像? (°∀°)ノ

這裡還有一個元件 lancercomet/vuexer 就是為 Vue 1.0 解決這個問題的!

完結撒花~

By LancerComet at 01:22, 2017.01.21.

相關文章