# 21. 模組化數據

# 數據模組化的用途

可將資料模組化、命名模組

# 模組化步驟

整體步驟如下:

  1. 建立一個 JS 檔案儲存資料
  2. 註冊模組
  3. 使用模組資料
  4. 取消註冊

# 建立一個 JS 檔案儲存資料

流程:

  • 建立一個新的 JS 檔案,儲存 Vuex 模組的資料。例如 ironStore.js
  • 將要放在該模組中的資料移到這個檔案。
  • 加入 namespaced: true ,使用命名空間 (namespace) 區分模組化資料。
//- ironStore.js

import axios from "axios";

export default ({
  namespaced: true, // 使用命名空間
  state: {
    day: 0,
    header: {
      title: "Vue.js 手牽手,一起嗑光全家桶",
      src: "https://vuejs.org/images/logo.svg"
    },
    list: []
  },
  mutations: {
    SETDAY(state, day) {
      state.day = day
    },
    SETLIST(state, list) {
      state.list = list
    }
  },
  actions: {
    GETLIST(context, day) {
      console.log(context)
      context.commit("SETDAY", day)
      return axios.get("/list.json").then(res => {
        context.commit("SETLIST", res.data)
      })
    }
  },
})

# 註冊模組

  • 建立 Vuex 的模組之後,要先引入模組,並註冊模組才能使用。
  • 引入模組的寫法有兩種:
    • 第一種:引入到 Vue 元件
      • 步驟
        • 在該 Vue 元件的 script 區塊引入。
        • 使用 registerModule() 註冊。
      • 補充
        • 這種寫法主要用於單頁或較不會被持續使用的模組。
        • 記得要在 beforeDestory 取消註冊。
    • 第二種:引入到 Vuex 主要檔案
      • 步驟
        • 在 Vuex 主要檔案引入。
        • 放到 modules 物件之中。
      • 補充
        • 這種直接註冊的模組通常有跨頁或會被持續使用的需求。
        • 這種寫法就不用在 beforeDestory 取消註冊。

第一種寫法要先 import 模組,然後使用 registerModule() 註冊,才能使用該模組中的資料:

//- components/Iron.vue

<script>
import ironStore from "../store/ironStore" // 引入

export default {
  // 略 ... 
  mounted(){
    let day = parseInt(this.$route.params.day) - 1
    this.$store.registerModule("list", ironStore) // 註冊
    this.$store.dispatch("list/GETLIST", day)
    document.addEventListener("keyup", this.changeHandler)
  },
  // 略 ...
}
</script>

第二種寫法則是引入到 Vuex 的 index.js,並放到 modules 進行管理:

//- store/index.js

import { createStore } from "vuex";
import list from "./ironStore" // 引入

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    list // 將引入的模組放到這裡進行管理
  },
});

# 使用模組資料

  • 如果要取用未模組化的 Vuex 資料,寫法如下:
    • this.$store.state.header
    • this.$store.dispatch("GETLIST", day)
  • 使用 Vuex 模組內的資料時,要加上此模組註冊的名稱。例如使用名為 list 的模組資料會改成:
    • this.$store.state.list.header
    • this.$store.dispatch("list/GETLIST", day)
//- components/Iron.vue

export default {
  computed: {
    day: {
      get(){
        return this.$store.state.list.day
      },
      set(val){
        this.$store.commit("list/SETDAY", val)
      }
    },
    header(){
      return this.$store.state.list.header
    },
    list(){
      return this.$store.state.list.list
    }
  },
  methods: {
    changeHandler(e){
      let day = this.day
      if(e.keyCode === 37){
        day = day == 0 ? day : day - 1
      } else {
        day = day == 29 ? day : day + 1
      }
      this.$router.replace({
        params: {
          day: day + 1
        }
      })
    }
  },
  mounted(){
    let day = parseInt(this.$route.params.day) - 1
    this.$store.registerModule("list", ironStore)
    this.$store.dispatch("list/GETLIST", day)
    document.addEventListener("keyup", this.changeHandler)
  },
  watch: {
    $route(){
      let day = parseInt(this.$route.params.day) - 1
      this.day = day
    }
  }
}

如果不需要其他資料來作判斷,更好的位置是在 beforecreate 階段進行註冊。

然而這個範例是在 mounted 階段註冊模組,執行後會報錯主要是因為還沒註冊進去就被打 mutation。為了讓畫面能正常呈現,我們可以將回傳結果進行一些判斷:

//- components/Iron.vue

  computed: {
    day: {
      get(){
        return this.$store.state.list ? this.$store.state.list.day : 0
      },
      set(val){
        this.$store.commit("list/SETDAY", val)
      }
    },
    header(){
      return this.$store.state.list ? this.$store.state.list.header : null
    },
    list(){
      return this.$store.state.list ? this.$store.state.list : []
    },
  }

# 取消註冊

如果是採用第一種註冊模組的寫法,要在元件銷毀前取消模組註冊,並且要記得關掉自己寫的監聽、計時器等事件。例如:

//- components/Iron.vue

  beforeDestory(){
    this.$store.unregisterModule("list")
    document.removeEventListener("keyup", this.changeHandler)
  },

如果是 click 這類的事件,可以不用移除監聽。但像計時器等事件就要移除,否則事件會在觸發後多次執行 (因為監聽器越來越多)。

# getters 取得指定天數的資料

實際上,我們只需要呈現指定天數的資料到畫面上而非全部,因此不需要將整個陣列傳入。我們可以先在 Vuex 透過 getters 取得指定天數的資料。

//- store/ironStore.js

  getters: {
    currentDay(state) {
      return state.list[state.day]
    }
  },

然後回到使用 "ironStore" 模組的 Vue 元件 "Iron.vue",將原本用到 this.$store.state.list 的部分,改為 this.$store.getters["list/currentDat"]

//- components/Iron.vue

  computed: {
    // 以上略
    // 原本傳入 state 的整個 list 陣列
    list(){
      return this.$store.state.list ? this.$store.state.list : []
    },
    // 改為取得 getters 中的 currentDay (上面的 list 就不需要了)
    currentDay(){
      return this.$store.state.list ? this.$store.getters["list/currentDay"] : null
    }
  },

而 template 中用到 list 的部分,也要改為取得 currentDay 的內容。

//- components/Iron.vue

<div class="description" v-if="currentDay">
  <div class="menuItem white" >
    <span class="number">{{ day + 1 }}</span>
    <span class="type">{{ currentDay.type }}</span>
    <a :href="currentDay.link" class="title">{{ currentDay.title }}</a>
  </div>
</div>