---
# System prepended metadata

title: vuex

---

# vuex
{%hackmd BJrTq20hE %}
流程：
![https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371900954_6.gif?alt=media&token=a92f02df-8800-4f4a-ac63-6690f9453e66](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371900954_6.gif?alt=media&token=a92f02df-8800-4f4a-ac63-6690f9453e66)
1. 安裝：
`npm i -S vuex@next`
2. 建立store>index.js
    ```
    |-- src
        |-- store
        |   |-- index.js
    ```
    ```javascript=
    //store>index.js
    import { createStore } from "vuex";

    export default createStore({
      state: {
        //相當於vue的data()
        isLoading: false,
        clickedTimes: 0,
        todos: [
          { id: 1, text: '...', done: true },
          { id: 2, text: '...', done: false }
        ]
      },
      mutations: {
        //專門用來控制state的methods
        //mutation 里面只允许同步的去修改 state 数据。
        Loaded(state) {
          state.isLoading = !state.isLoading;
        },
        addTimes(state) {
          state.clickedTimes += 1;
        },
      },
      actions: { //相當於vue的methods(只允許異步)
        addTimes({ commit }) {
            commit('addTimes') //呼叫mutation的addTimes
        },
      },
      getters: { //就是 store 裡面的 computed
        doneTodos: state => { //Getter 接受 state 作为其第一个参数
          return state.todos.filter(todo => todo.done)
        }
      },
    });
    ```
    ![](https://i.imgur.com/a7bmFTa.png)
3. 把store掛到vue上：
    ```javascript=
    //main.js
    import { createApp } from "vue";
    import store from "../store";
    import App from "./App.vue";

    const app = createApp(App);
    app.use(store);
    app.mount("#app");
    ```
4. 使用
    - optonal
        ```javascript=
        <template>
          <div>
            <p>Loading: {{ isLoading }}</p>
            <button @click="reverseLoad(); addTimes()">Reverse</button>
            <p>Button Clicked Times: {{ clickedTimes }}</p>
          </div>
        </template>

        <script>
        import { mapState } from "vuex";
        export default {
          name: "app",
          computed: {
            ifLoading() {
              // 在這邊吐回state裡面的isLoading
              return this.$store.state.isLoading;
            },
            clicked() {
              // 抓 state 的 clickedTimes  
              return this.$store.state.clickedTimes;
            }
          },
          //如果要取得多個state可以用mapState
          computed: {
            ...mapState([
              // 需要的state在這邊
              'isLoading',
              'clickedTimes'
            ]),
            doneTodosCount() { //取得getter資料
              return this.$store.getters.doneTodosCount
            }
          },
          methods: {
            reverseLoad() {
              this.$store.commit("Loaded"); // 在這邊執行store裡面的Loaded這個mutation
            },
            addTimes() {
              this.$store.commit("addTimes");
            }
          }
        };
        </script>
        ```
    - composition
        ```javascript=
        import { useStore } from 'vuex'
        const store = useStore()

        const toDoList = computed(() => store.state.todos)
        ```
    mapState格式可為：(map系列為把store值傳進來)
    ```javascript=
    //可以改用物件來指定state裡面的值
    computed: mapState({
        ifLoading: "isLoading",
        Times: "clickedTimes"
    }),

    //or函式
    computed: mapState({
      ifLoading(state) {
        return state.isLoading;
      },
      Times(state) {
        return state.clickedTimes;
      }
    }),
    //可簡化為
    computed: mapState({
      ifLoading: state => state.isLoading,
      Times: state => state.clickedTimes
    }),
    ```
    通常我們的 computed 裡面，不會只有 mapState，也會有別的 computed 要使用，可以使用 ES6 的 … 來達成：    

    ```javascript=
    computed: {
      otherfn() {
        return "asdf";
      },
      ...mapState({
        ifLoading: state => state.isLoading,
        Times: state => state.clickedTimes
      })
    },
    ```
    多個 mutations 也有 mapMutations 可以同理使用：(皆需要import進來)
    ```javascript=
    import { mapState, mapMutations } from "vuex";
    export default {
      name: "app",
      // 陣列、物件指定方法和mapState一樣
      methods: mapMutations(["Loaded", "addTimes"])
    };
    ```
    多個 actions 也有 mapActions 可以使用
## commit、dispath
- commit 拿來呼叫 mutations
- dispatch 拿來呼叫 actions
    actions 不能直接變動 state，只有 mutations 可以更動 state
    ```javascript=
    //store.js
    import { createStore } from "vuex";
    import axios from "axios";

    export default createStore({
      state: {
        // state 裡面的 Loaded 預設為 false ，在 axios 成功 get 到 user api 的之後，再更改為 true
        Loaded: false,
      },
      actions: {
        GetUser(context) {
          axios
            .get("https://randomuser.me/api/")
            .then(function (response) {
              console.log(response);
              // context.commit 用來呼叫 mutations，
              // context.dispatch 用來呼叫另外一個 actions。
              context.commit("MyMutations");
              context.dispatch("AnotherActions");
            })
            .catch(function (error) {
              console.log(error);
            });
        },
        AnotherActions() {
          console.log("Another Actions run!");
        },
      },
      mutations: {
        MyMutations(state) {
          console.log("MyMutations run!");
          // 抓到user之後，將state的loaded改為true
          state.Loaded = true;
        },
        SetFalse(state) {
          state.Loaded = false;
        },
      },
    });

    ```
    PS：context可以用 ES6 解構的方法將 context 裡面的 commit, dispatch 解構出來使用：
    ```javascript=
    GetUser({ commit, dispatch }) {
      axios.get('https://randomuser.me/api/')
        .then(function (response) {
          console.log(response);

          // 使用解出來的
          commit('MyMutations')
          dispatch('AnotherActions')
        })
        .catch(function (error) {
          console.log(error);
        })
    },
    ```
    ```javascript=
    //app.vue
    <template>
      <div id="app">
        // state 的 Loaded
        <p>random user api Loaded: {{ userLoaded }}</p>// Reload API
        <button @click="Reload">Reload</button>
      </div>
    </template>

    <script>
    export default {
      name: "app",
      data() {
        return {};
      },
      mounted() {
        // 1.  頁面讀取完成時，吃 randomuser API
        this.$store.dispatch("GetUser");
      },
      computed: {
        // 2. 將 state 中的 Loaded 用 computed 抓出來給 userLoaded 做使用(取值)
        userLoaded() {
          return this.$store.state.Loaded;
        }
      },
      methods: {
        // 3. Reload 按鈕按下去的時候，把 state 的 Loaded 改回 false，然後再執行一次 GetUser 這個 actions
        Reload() {
          this.$store.commit("SetFalse");
          this.$store.dispatch("GetUser");
        }
      }
    };
    </script>
    ```
    actions 跟 mutations 的用法差不多，但是我們要記得 ==**mutations 必須是同步執行**==，而 ==actions 則可以**異步執行**==。

## getters：就是 store 裡面的 computed
Getters 簡單說就是可以把 state 處理過後再丟出去
```javascript=
//store.js
import { createStore } from "vuex";
import axios from "axios";

export default createStore({
  state: {
    Loaded: false,
    clickedTimes: 0,
    users: [],
  },
  actions: {
    GetUser({ commit }) {
      axios.get("https://randomuser.me/api/?results=5").then(function (res) {
        var data = res.data.results;
        commit("dataLoaded");
        // 把抓回來的data發給mutations
        commit("setUserInfo", data);
      });
    },
    ClickedActions({ commit }, payload) {
      commit("addTimes", payload);
    },
  },
  mutations: {
    dataLoaded(state) {
      state.Loaded = true;
    },
    SetFalse(state) {
      state.Loaded = false;
    },
    addTimes(state, payload) {
      state.clickedTimes = state.clickedTimes + payload;
    },
    // 把拿到的data丟進state裡面
    setUserInfo(state, payload) {
      state.users = payload;
    },
  },
  getters: {
    FemaleNum(state) {
      return state.users.filter((item) => item.gender == "female").length;
    },
    //getters可以帶入三種參數，state、getters、函式
    MaleNum(state, getters) {
      return state.users.length - getters.FemaleNum;
    },
    //回傳函式
    returnFn(state) {
      return function DetectGender(gd) {
        if (state.users.filter((item) => item.gender == gd).length > 2)
          console.log("There are over 2 " + gd + " in data");
        else console.log("No over 2 " + gd + " in data");
      };
    },
  },
});

```
getters 除了帶入 state 之外，總共可以帶入三種參數：
- state
- 其他 getters
- 函式
## map-getters
- optional：
    ```javascript=
    //app.vue
    <template>
      <div id="app">
        <p>Loading: {{ Loaded }}</p>
        <button @click="Reload(); addTimes()">reload</button>
        <p>Button Clicked Times: {{ clickedTimes }}</p>
        <p>Female number: {{ FemaleNum }}</p>
        <p>Male numner: {{ MaleNum }}</p>
        <br />// 資料中有沒有超過兩個女生？
        <label>Are there more than 2 women in data?</label>
        <button @click="DetectGender('female')">Detect</button>
        <br />// 資料中有沒有超過兩個男生？
        <label>Are there more than 2 men in data?</label>
        <button @click="DetectGender('male')">Detect</button>
      </div>
    </template>

    <script>
    import { mapState, mapActions, mapGetters } from "vuex";
    export default {
      name: "app",
      mounted() {
        // 1.  頁面讀取完成時，吃 randomuser API
        this.$store.dispatch("GetUser");
      },
      computed: {
        ...mapState(["Loaded", "clickedTimes", "users"]),
        //陣列寫法
        ...mapGetters(["FemaleNum", "MaleNum"]), //從getter取得資料
      },
      methods: {
        Reload() {
          this.$store.commit("SetFalse");
          this.$store.dispatch("GetUser");
        },
        addTimes() {
          this.$store.commit("addTimes", 1);
        },
        DetectGender(gd) {
          this.$store.getters.returnFn(gd);
        },
      }
    };
    </script>
    ```
- mapGetters vue3寫法：
https://medium.com/geekculture/mapgetters-with-vue3-vuex4-and-script-setup-5827f83930b4
    ```javascript=
    // assets/js/map-states.js
    import { computed } from "vue";
    import { useStore } from "vuex";
    const mapState = () => {
      const store = useStore();
      return Object.fromEntries(
        Object.keys(store.state).map((key) => [
          key,
          computed(() => store.state[key]),
        ])
      );
    };
    const mapGetters = () => {
      const store = useStore();
      return Object.fromEntries(
        Object.keys(store.getters).map((getter) => [
          getter,
          computed(() => store.getters[getter]),
        ])
      );
    };
    const mapMutations = () => {
      const store = useStore();
      return Object.fromEntries(
        Object.keys(store._mutations).map((mutation) => [
          mutation,
          (value) => store.commit(mutation, value),
        ])
      );
    };
    const mapActions = () => {
      const store = useStore();
      return Object.fromEntries(
        Object.keys(store._actions).map((action) => [
          action,
          (value) => store.dispatch(action, value),
        ])
      );
    };
    export { mapState, mapGetters, mapMutations, mapActions };
    ```
    使用：
    ```javascript=
    import { mapState, mapGetters, mapMutations, mapActions } from '../map-state'

    // computed properties
    const { count } = mapState()
    const { countIsOdd, countIsEvent } = mapGetters()
    // commit/dispatch functions
    const { countUp, countDown } = mapMutations()
    const { getRemoteCount } = mapActions()
    ```
## Modules
如果專案規模比較龐大，store 裡面的 state 可能會變得越來越肥，可能會有會員資訊、訂單資訊、商品資訊、最新消息…等，所以 Vuex 允許我們使用 **Modules**來解決這個問題，**將 store 中各類的 state 分類管理**。
[教學](https://medium.com/itsems-frontend/vue-vuex4-modules-ddb3eec6b834)
```javascript=
const moduleUser = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const modulePurchase = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

// 在 new Vuex Store 的時候，把modules加進來
const store = new Vuex.Store({  //(vue2寫法)
  modules: {
    user: moduleUser,
    purchase: modulePurchase
  }
})
export default createStore({
  modules: {
    user: moduleUser,
    purchase: modulePurchase
  }
}
```
## Module — Getters
Module 裡面的 Getters 會有這些參數：`state, getters, rootState, rootGetters` ，前面的 `state, getters` 一樣是 module 自己的 state 跟 getters，`rootState, rootGetters` 則表示回到**根部**(new Vuex.Store 那一層)，舉例：
```javascript=
//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const moduleUser = {
  //  自己的state
  state: {
    name: "emma",
    age: 18
  },
  getters: {    
    // 拿自己的state
    GetAge(state) {
      return state.age
    },    
    // 拿根部的state
    GetRealAge(state, getters, rootState) {
      return rootState.age
    },    
    // 另一個module的state 
    GetNextAge(state, getters, rootState) {
      return rootState.another.NextyearAge
    }    
  }
}

// 另一個module
const moduleAnother = {
    state: {
        NextyearAge: 21
    },
}

const store = new Vuex.Store({
  // 根部的state
  state: {
    age: 20,
    gender: "female"
  },
  // 註冊modules
  modules: {
    user: moduleUser,
    another: moduleAnother
  }
})

export default store;
```
這邊要注意一個小地方，在 moduleUser 裡面的 getters 這邊，假設如範例我要使用 `rootState` 這個參數，前面兩個 `state, getters` 也要帶上去，如果只寫 `rootState` 就會被當成自己的 state，他是依序判斷參數的
- 使用 (此範例有使用 vuex-shared-mutations)
    ```jsx=
    const store = useStore();
    const isStudent = computed(() => store.getters["auth/getRole"].isStudent);
    ```
## Module — Actions
actions 在第二篇說明 actions 的時候有提到他可以帶入的參數，context 原本有 `commit, dispatch, state, getters`，在 modules 裡面，和 getters 一樣也有 `rootState, rootGetters` 可以用

## 區域、全域、namespaced
使用了 module 之後，**module 們的 actions, mutations, getters 都是全域共用的**，全域共用是什麼意思呢？意思就是如果 moduleA 和 moduleB 都有一個 actions 叫做 `sayHi`，那麼我在 component dispatch `sayHi` 的話，兩個 module 的 `sayHi` 都會執行，下面用範例來看看：
```javascript=
const moduleUser = {
    namespaced: true, //命名空間
    actions: {
        sayHi() {
            console.log('Hello from moduleUser')
        }
    }
}
const moduleAnother = {
    namespaced: true,
    actions: {
        sayHi() {
            console.log('Hello from moduleAnother')
        }
    }
}
```
```javascript=
mounted() {
    //假設上面沒有設定namespaced，會同時呼叫
    this.$store.dispatch("sayHi");

    //加上了 namespaced 這個設定之後，在 component 要 dispatch 這個 actions 前面就要加上 module 的名稱
    this.$store.dispatch("user/sayHi");
    this.$store.dispatch("another/sayHi");
    
    //用 mapGetters 呼叫 moduleUser 的 getters，其他以此類推
    ...mapGetters({
        GetAge: "user/GetAge",
        GetRealAge: "user/GetRealAge",
        GetNextAge: "user/GetNextAge"
    })
},
```
補充：`...mapGetters(["FemaleNum", "MaleNum"])`為原本寫法。
這裡把剛剛 mapGetters 用法從**陣列**轉為**物件**了，因為原本陣列的用法，template 上的名字需要和 getters 一樣，現在因為 namespaced 的關係，getters 的名字前面需要加上 module 的名字，所以就改用物件把 template 的 getters 名稱指定到 module 的 getters。

- 接下來在 store.js 中，在 moduleAnother 的 actions `addYear` commit 會帶上三個參數：`mutations, payload, {root:true}` ：
    ```javascript=
    commit('user/addAge', 2, { root: true })
    ```
    第一個參數一樣是要呼叫的別的 module 的 mutations，第二個參數則為 payload，如果不需要 payload，也可以寫 `null` 就好，第三個 `{ root: true }` 表示為**根部**，就是從底部找的意思，**開啟 namespaced 後，module 之間的 actions, getters, mutations 之間要互相呼叫都要記得加上**`{ root: true }`，如果沒有加上這句話，vuex 會當作你還是要呼叫自己的東西，就會錯了
## Module — 使用 mapState, mapMutations, mapActions, mapGetters簡化寫法
在 component 中，如果有很多個 state 要指定，所以也可以將
```javascript=
...mapState({  
  MyName: state => state.user.name,  
  gender: state => state.user.gender,  
  addr: state => state.user.addr  
}),
```
改寫為：
```javascript=
...mapState("user", {  
  MyName: state => state.name,  
  gender: state => state.gender,  
  addr: state => state.addr  
}),
```
**在 maptState 前面加入一個 module 名的字串**，就可以指定要找的 module 名稱了。
如果 template 上的名稱跟 state 裡面的名稱一樣，改為陣列的話，會更簡短：
```javascript=
...mapState("user", ["name", "gender", "addr"]),
```
### 或是再更精簡一點：
```javascript=
//app.vue
<template>
  <div id="app">    
    // state
    <p>hi, I'm {{name}}</p>
    <p>I'm a {{gender}}</p>
    <p>and I live in {{addr}}</p>
    
    // getters
    <p>I'm {{GetAge}} years old.</p>
    <p>Just kidding, I'm actually {{GetRealAge}} years old.</p>
    <p>And next year I'll be {{GetNextAge}} years old.</p>
</template>

<script>
    // import createNamespacedHelpers
    import { createNamespacedHelpers } from "vuex";
    // 並直接指定使用 user 這個 module，
    const { mapState, mapGetters } = createNamespacedHelpers("user");
    export default {
      name: "App",
        computed: {
        ...mapState(["name", "gender", "addr"]),
        ...mapGetters(["GetAge", "GetRealAge", "GetNextAge"])
      },
    };
</script>
```
```javascript=
//store.js
import { createStore } from "vuex";

const moduleUser = {
  namespaced: true,
  //  自己的state
  state: {
    name: "emma",
    gender: "female",
    addr: "Taipei",
    age: 18,
  },
  getters: {
    // 拿自己的state
    GetAge(state) {
      return state.age;
    },
    // 拿根部的state
    GetRealAge(state, getters, rootState) {
      return rootState.age;
    },
    // 另一個module的state
    GetNextAge(state, getters, rootState) {
      return rootState.another.NextyearAge;
    },
  },
};

// 另一個module
const moduleAnother = {
  state: {
    NextyearAge: 21,
  },
};

export default createStore({
  // 根部的state
  state: {
    age: 20,
    gender: "female",
  },
  // 註冊modules
  modules: {
    user: moduleUser,
    another: moduleAnother,
  },
});
```
## vuex 重整數據丟失解決方案
https://blog.csdn.net/bidepanm/article/details/124686409