---
# System prepended metadata

title: VUEX
tags: [Vue.js]

---

---
tags: Vue.js
---

# VUEX

## 安裝

打開終端機輸入 `npm i --save vuex` 即可

## 結構說明

`Vuex` 是一個專為 `Vue` 開發的狀態管理模式 它可以集中存儲管理應用的所有組件的狀態 通常用於大型結構或網站的項目上

它的結構不同於 `vue` 但又類似於 `vue`

結構如下：

  - `state` 保存資料狀態用 類似於 `vue` 中的 `data`

  - `actions` 進行非同步的行為方法及獲取資料 類似於 `vue` 中的 `methods`

  - `getter` 資料呈現的方式 類似於 `vue` 中的元件

  - `mutation` 該變資料內容的方法 跟  `vue` 中的 `methods` 不同 在 `vuex` 中無法在獲取資料的同時做更改 需通過  `mutation` 才能更改資料狀態

    * 這裏建議把 `mutations` 的方法名統一使用全大寫取名 另外 `mutations` 通常名字會跟 `state` 取一樣的 差別在一個全大寫一個全小寫 較好辨認

## 使用

首先在項目中的 `src` 資料夾裡面增加一個資料夾 `store` 它是用來放彙整專用的檔案的 該檔案我們取為 `index.js`

打開 `index.js` 在裡面寫入以下代碼：

```js

import Vue from "vue"
import Vuex from "vuex"

Vue.use(Vuex)

export default new Vuex.Store({})

```

接下來我們到項目中的 `main.js` 中引入 `store` 資料夾 引入方式同 `router` 一樣 先 `import` 然後在 `new Vue` 實例中加入 `store`

這樣就完成使用了

## 通過 Vuex 切換 isLoading 狀態

接下來打開 vuex 的 `index.js` 然後在 `Vuex.Store({})` 中添加結構

如下：

```js

export default new Vuex.Store({
  state: {
    isLoading: false
  },
  actions: {
    // 創建一個方法為 updateLoading 其中參數的 context 為 vuex 的參數，status 為自定義變量名稱
    updateLoading(context, status) {
      // 用 context.commit 方法切換資料狀態 參數1即 mutations 的方法
      context.commit('LOADING', status)
    }
  },
  mutations: {
    // 創建一個方法為 LOADING 其中參數的 state 為 vuex 的保存資料狀態那個 state 參數的 status 就是剛才上面 actions 的變量
    LOADING(state, status) {
      // 然後用傳進來的變量改變 isLoading 的值 如下
      state.isLoading = status
    }
  }
})

```

然後回到 `App.vue` 中 把 `data` 中的 `isLoading` 拿掉

找到每一個 `methods` 需要切換 `isLoading` 的地方 改寫為 

```js

// isLoading = true
vm.$store.dispatch("updateLoading", true); 
// isLoading = false
vm.$store.dispatch("updateLoading", false);

```

最後在 `export default{}` 裡添加上

```js

computed: {
  isLoading() {
    return this.$store.state.isLoading;
  },
},

```

即完成

## 嚴謹模式

在 vuex 中可以通過在 `state` 前面添加一行 `strict: true` 來讓 vuex 自動偵測語法上的錯誤 並在 `Console` 中給出警告

該模式是用來保證每次資料狀態的變更都可以被 `vue` 的開發者工具所偵測到

## 把 vue 改成 vuex

首先我們將原本在 vue 實例中的 `methods` 方法複製到 vuex 的 `index.js` 中

  * `methods` 方法複製到 `actions` 裡面

  * `methods` 更改 `data` 的地方複製到 `mutations` 裡面

  * `methods` 需要操作到的 `data` 則新增到 `state` 裡面

接下來給所有 `actions` 方法各自添加上 `context` `payload` 這兩個參數

把操作 `data` 的地方藉由 `context.commit()` 方法呼叫 `mutations` 對應的資料

如下：

```js

actions:{
  getProducts(context, payload) {
    const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/products/all`
    context.commit('LOADING', true)
    this.$http.get(url).then((response) => {
      console.log("取得產品列表:", response)
      payload = response.data.products
      context.commit('PRODUCTS', payload)
      context.commit('CATEGORIES', payload)
      context.commit('LOADING', false)
    })
  }
}

```

這裏要注意的是

* `this.$http` 原本是在實例中使用 `ajax` 的地方

在 `vuex` 中因為我們沒有引入 `axios` `vue-axios` 所以 `this.$http` 是找不到的

要解決該問題我們只需將 `axios` 引入到 `index.js` 中 並把 `this.$http` 改成 `axios` 即可

* 另外如果該方法需傳遞超過一個以上的參數則可用 `{}` 把多個參數包起來

接下來給所有 `mutations` 方法各自添加上 `state` `payload` 這兩個參數

通過 `state.xxx` 呼叫對應的資料 並賦值為傳進來的 `payload` 即可

如下：

```js

mutations: {
  PRODUCTS(state, payload) {
    state.products = payload
  },
  CATEGORIES(state, payload) {
    const categories = new Set()
    payload.forEach((item) => {
      categories.add(item.category)
    })
    state.categories = Array.from(categories)
  }
}

```

最後回到 vue 實例中 把原本的 `methods` 改成 `this.$store.dispatch('actions名稱')`

然後把 `data` 中的 `products` 與 `categories` 移除 並在 vue 的 `computed` 中添加以下代碼：

```js

computed:{
  products() {
    return this.$store.state.products;
  },
  categories() {
    return this.$store.state.categories;
  },
}

```

以上就是一個完整的修改範例了

## mapXXX 運用方法

之前提過在 `vuex`還有一個 `getters` 類似於 `vue` 中的 `computed`

它的使用方式如下：

```js

// vuex 的 index.js 檔
getters: {
  products(state) {
    return state.products
  },
  categories(state) {
    return state.categories
  },
}

// .vue 檔
import { mapGetters } from "vuex";

computed:{
  ...mapGetters(["products", "categories"]),
}

```

另外 在 `vuex` 中的其他結構也可以通過上述這種 `mapXXX` 方式來使用

但要注意的是 ***該方法不適用於需傳遞參數的地方***

若需要傳遞參數則還是要通過 `vm.$store.dispatch("方法名", 參數)` 才可以

## vuex 模組化

我們把範例檔案以購物車相關與商品相關拆分出兩個模塊 分別為 `PModule` 與 `CModule` 

首先在 `store` 資料夾內新增 `PModule.js` 與 `CModule.js` 然後在 `index.js` 的結構最下方添加一個屬性 `modules`

接下來把 `PModule.js` 與 `CModule.js` 引入到 `index.js` 中 將引入的名稱設為 `modules` 屬性的值

如下：

```js

export default new Vuex.Store({
  ...,
  modules: {
    PModule,
    CModule
  }
})

```

然後我們開啟 `PModule.js` 與 `CModule.js` 先引入 `axios` 接下來一樣寫入結構

範例如下：

```js

import axios from "axios"
export default {
  state:{},
  actions:{},
  mutations:{},
  getters:{}
}

```

把原本在 `index.js` 中有關購物車的部分全部改放到 `CModule.js` 中

把原本在 `index.js` 中有關商品的部分則全部改放到 `PModule.js` 中

接下來稍微說明一下模組中的結構問題：

* 在模組中的 `state` 是區域性的 即只在該模組中有被定義 若你在 `.vue` 檔中通過 `this.$store.state.xxx` 則無法獲取到模組中的 `state`

  - 若要在 `.vue` 檔中取得模組中的 `state` 屬性則改為：

  ```js
  
  vm.$store.state.PModule.products;
  
  ```

* 在模組中的其他結構是全域的 即在 `.vue` 檔中通過 `this.$store.dispatch("方法名")` 是可以的

* 想把模組中的所有結構統一改成區域性是可以的 只需在模組結構的第一行加上 `namespaced: true` 即可

  - 這樣做的好處是當你在 `index.js` 或其他模組檔案中有任一結構出現重複的名字時不會出錯

  - 這樣做以後在 `.vue` 中就不能直接通過 `this.$store.dispatch("方法名")` 調用方法了

    + 解決方法如下：

    ```js
    
    // 原本在 App.vue 中的獲取產品列表為 
    getProducts()
    // 在 index.js 中的獲取產品列表需於 .vue 檔中改為 
    this.$store.dispatch('getProducts')
    // 在 PModule.js 中的獲取產品列表需於 .vue 檔中改為 
    this.$store.dispatch('PModule/getProducts')
    
    ```

* 最後補充 `mapGetters` 與 `mapActions` 在模組化後的獲取方法：

```js

...mapGetters("PModule", ["products", "categories"]);
...mapActions("PModule", ["getProducts"]);

```