筆記 - Vuex
範例使用 vue-cli 建立,對官網的教學文章筆記下Vuex的使用方式
vue create vuexsample
- State - store 內儲存的狀態
- mutations - 相檔於 method
- 可以直接操作 state
- 只能處理同步方法
- getters - 相當於 computed
- 可以直接操作 state
- actions - 也是類似 method
- 可以處理非同步方法
- 無法直接修改 state,必須透過 mutations
- 可以取得當前的 state(store or module)
Vuex 基本使用
安裝 vuex
npm i vuex -s
建立一個 store.js,用來存放 Vuex 相關內容
- 必須加上 Vue.use(Vuex),若不是使用webpack之類的打包方式,可以不用這行
import Vuex, {Store} from "vuex"; import Vue from "vue"; Vue.use(Vuex); const store = new Store({ state: { count: 0 }, }); export default store;
在建立 vue 實體時把 store 一併給入
import Vue from 'vue'
import App from './App.vue'
import store from "./Store";
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App),
}).$mount('#app')
調整 store.js 加上 state 和 mutatuins,稍後會在 .vue 使用到
- State 相當於在 vuex 上保存的變數
- mutations 相當於 method,負責修改 state
- mutations 的第一個參數是 state,用來存取 store 內的 state
const store = new Store({
state: {
count: 0
},
mutations: {
add(state) {
state.count++;
}
}
});
在 .vue 上開始使用 vuex
- 前面在 new Vue() 時傳入了 store進去,因此底下的各個組件都可以透過 this.$store 存取 store 的內容
- store 內的 state 可以使用 this.$store.state.count,從取得 count 的值
- store 內的 mutations 可以使用 this.$store.commit("<name>") 來呼叫
<template>
<div id="app">
<div>count: {{count}}</div>
<button @click="add">Add</button>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
add() {
this.$store.commit("add");
}
}
}
</script>
現在已經完成簡單的計數器,也可以透過 DevTools 看到 vuex 內的內容
接著為 add 添加一個參數,讓計數器不是累加1
- mutations 的第二個參數是使用端傳入的參數,這邊有幾種寫法
1.
// store.js
mutations: {
add(state, count) {
state.count += count;
}
}
// .vue
methods: {
add() {
this.$store.commit("add", 2);
}
}
2.
// store.js
mutations: {
add(state, payload) {
state.count += (payload.count);
}
}
// .vue
methods: {
add() {
this.$store.commit("add", {
count: 2
});
}
}
3.
// store.js
mutations: {
add(state, payload) {
state.count += (payload.count);
}
}
// .vue
methods: {
add() {
this.$store.commit({
type: "add"
count: 2
});
}
}
Getters
若某些邏輯是必須對 state 運算取得的,可以使用 getters
- 第一個參數是 state
- 透過 this.$store.getters.<name> 取得
// store.js
getters: {
length(state) {
return state.count + 100;
}
}
// .vue
computed: {
length() {
return this.$store.getters.length;
}
},
gatters 第二個參數是 getters,代表可以從裡面取得其他的 getter
getters: {
length(state, getters) {
return state.count + getters.test + 100;
},
test(state) {
return state.count + 5;
}
}
getters 也可以返回一個方法
getters: {
length(state, getters) {
return state.count + getters.test + getters.func(0) + 100;
},
test(state) {
return state.count + 5;
},
func() {
return (value) => value + 50;
}
}
actions
類似於 method,跟 mutations 不同的是 action 可以處理非同步操作,代表可以加上 async/await 之類的相關邏輯,並且呼叫 mutations 改變 state
- 第一個參數是 context,代表的是當下位置的 store 或是 module
- 可以從 context.commit(<name>) 呼叫 mutations
- 透過 this.$store.dispatch("<name>") 呼叫 action
// store.js
actions: {
cut(context) {
context.commit("add", {
count: -1
});
}
}
// .vue
methods: {
cut() {
this.$store.dispatch("cut");
}
}
也可以對 context 解構,僅取得 commit 來使用
actions: {
cut({commit}) {
commit("add", {count: -1});
}
}
與 mutations 同樣的,有各種呼叫方式
cut() {
this.$store.dispatch({
type: "cut",
count: -2
});
}
Map 系列
一旦需要從 store 取用的東西一多時,每個都要寫一次 this.$stroe... 挺煩人的,vuex 針對這部份提供了幾個工具可以使用
MapState
從 vuex 引入 mapState,並且調整下 .vue,mapState讓取用 state 可以更方便
import {mapState} from "vuex";
export default {
name: 'App',
computed: mapState({
count: state => state.count,
}),
methods: {
add() {
this.$store.commit("add", 2);
}
}
}
除了上面的方式外,mapState也有其他的寫法
computed: mapState(["count"]),
computed: {
...mapState(["count"]),
},
computed: {
...mapState({
count: 'count'
})
},
其他的 computed 繼續往後添加即可
computed: {
...mapState({
count: 'count'
}),
test() {
return "";
}
},
mapMutations
相對於 mapState,mutations 也有 mapMutations 可以使用,同樣的也有 state 的各種寫法
import {mapMutations, mapState} from "vuex";
export default {
name: 'App',
computed: {
...mapState({
count: 'count'
}),
},
methods: {
...mapMutations({
add: 'add'
}),
}
}
mapGetters
使用方式同上
import {mapGetters, mapState} from "vuex";
export default {
name: 'App',
computed: {
...mapGetters({
length: 'length',
test: 'test',
func: 'func',
}),
}
}
mapActions
使用方式同上
import {mapActions, mapGetters, mapState} from "vuex";
export default {
name: 'App',
methods: {
...mapActions({
cutAction: "cut"
}),
}
}
Modules
當 store 的內容越來越多,在管理上就會越不便,這時候可以使用 modeuls 來拆分 store,把各個區塊拆分獨立管理
把 Store 所有內容都拆出一個 Modules
- 在 store 內使用 modules,來指定要加入的 modules
- modules 加上 namespace: true,在後續使用會比較方便
- modules 的內容跟在 store 時幾乎一樣,部分差異會在後面提到
修改使用端
- 取得 state 需要多加上 namespace,變成 this.$store.<namespace>.<name>
- 這邊的 my 則是在 store 的 modules 前定義的名稱
- 並且在 modules 內加上 namespace=true 來指定需要 namespace
- getters、mutations、action,則是也是在前面加上 namespace,變成 ["<namespace>/<name>"]
// store.js
import Vuex, {Store} from "vuex";
import Vue from "vue";
Vue.use(Vuex);
const myStore = {
namespaced: true,
state: {
count: 0,
name:"Test",
},
mutations: {
add(state, payload) {
state.count += payload;
}
},
actions: {
cut(context, payload) {
context.commit("add", payload.count);
}
},
getters: {
length(state) {
return state.name.length;
}
}
}
const store = new Store({
modules: {
my: myStore
},
});
export default store;
// .vue
export default {
name: 'App',
computed: {
count() {
return this.$store.state.my.count;
},
length() {
return this.$store.getters["my/length"];
},
},
methods: {
add() {
this.$store.commit("my/add", 2);
},
cut() {
this.$store.dispatch("my/cut", {
count: -2
});
}
}
}
從 modules 呼叫 root 的方法
若需要從 modules 呼叫 root 的 mutations 時可以在加上第三的參數,指定呼叫 root
const myStore = {
namespaced: true,
actions: {
addRoot(context) {
context.commit("add", 3, {root: true});
}
}
}
從 modules 取得 root 的 state 或 getters
getters - 在modules的 getters 提供了幾個參數,最後面兩個分別是 rootState 和 rootGetters,從名字來看就可以知道用法了
getters: {
rootCount(state, getters, rootState, rootGetters) {
return rootState.rootCount;
},
}
actions - 與 getters 不一樣的是,action 的參數 context 本身就包含了 rootState 和 rootGetters 可以使用
actions: {
cutRoot(context) {
context.commit("add", -context.rootState.count);
}
},
接著我們把前面的範例的 vue 全部依照 modules 做調整後
<template>
<div id="app">
<div>count: {{count}}</div>
<button @click="add(2)">Add</button>
<button @click="cut(-2)">Cut</button>
<button @click="cutRoot">Cut Root</button>
<div>length: {{length}}</div>
<hr>
<button @click="addRoot(2)">Add Root</button>
<div>rootCount: {{rootCount}}</div>
<hr>
<button @click="addTest(3)">Add Root</button>
</div>
</template>
<script>
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
export default {
name: 'App',
computed: {
...mapState({
count: state => state.my.count
}),
...mapGetters({
length: "my/length",
rootCount: "my/rootCount"
}),
},
methods: {
...mapMutations({
add: "my/add",
addRoot: "add",
}),
...mapActions({
cut: "my/cut",
cutRoot: "my/cutRoot",
addTest: "my/addRoot"
}),
}
}
</script>
import Vuex, {Store} from "vuex";
import Vue from "vue";
Vue.use(Vuex);
const myStore = {
namespaced: true,
state: {
count: 0,
name: "Test",
},
mutations: {
add(state, payload) {
state.count += payload;
}
},
actions: {
cut(context, payload) {
context.commit("add", payload);
},
cutRoot(context) {
context.commit("add", -context.rootState.rootCount);
},
addRoot(context) {
context.commit("add", 3, {root: true});
}
},
getters: {
length(state) {
return state.name.length;
},
rootCount(state, getters, rootState) {
return rootState.rootCount;
},
}
}
const store = new Store({
modules: {
my: myStore
},
state: {
rootCount: 0
},
mutations: {
add(state, payload) {
state.rootCount += payload;
}
},
});
export default store;
namespace 看得很煩人,可以拿掉嗎?
每一個使用的地方都要掛上 namespace 在前面,使用上挺煩人的,有幾種方式可以讓他更好看一些
map系列的參數
當第一個參數改為一個字串時,代表的則是 namespace,結果會變成
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
export default {
name: 'App',
computed: {
...mapState({
count: state => state.my.count
}),
...mapGetters("my", {
length: "length",
rootCount: "rootCount"
}),
},
methods: {
...mapMutations("my", {
add: "add",
}),
...mapMutations({
addRoot: "add",
}),
...mapActions("my", {
cut: "cut",
cutRoot: "cutRoot",
addTest: "addRoot"
}),
}
}
透過 vuex 內的 createNamespacedHelpers
vuex 提供了一個工具可以直接取得指定的 namespace 的 map
const {mapState, mapMutations, mapGetters, mapActions} = createNamespacedHelpers("my");
其調整結果會變成如下,這樣看就簡潔了很多
import {createNamespacedHelpers} from "vuex"
const {mapState, mapMutations, mapGetters, mapActions} = createNamespacedHelpers("my");
export default {
name: 'App',
computed: {
...mapState({
count: state => state.count
}),
...mapGetters({
length: "length",
rootCount: "rootCount"
}),
},
methods: {
addRoot(payload) {
this.$store.commit("add", payload);
},
...mapMutations({
add: "add",
}),
...mapActions({
cut: "cut",
cutRoot: "cutRoot",
addTest: "addRoot"
}),
}
}
[非強制] 根據官方建議,可以把各個命名都改用 const 取代,方便後續的維護
直接貼上結果
export const MyLength = "length";
export const MyRootCount = "rootCount";
export const RootAdd = "add";
export const MyAdd = "add";
export const MyCount = "count";
export const MyName = "name";
export const RootCount = "rootCount";
export const MyCut = "cut";
export const MyCutRoot = "cutRoot";
export const MyAddRoot = "addRoot";
import {createNamespacedHelpers} from "vuex"
import {MyAdd, RootAdd} from "./MutationTypes";
import {MyLength, MyRootCount} from "./GetterTypes";
import {MyAddRoot, MyCut, MyCutRoot} from "./ActionTypes";
import {MyCount} from "./StateTypes";
const {mapState, mapMutations, mapGetters, mapActions} = createNamespacedHelpers("my");
export default {
name: 'App',
computed: {
...mapState({
count: MyCount
}),
...mapGetters({
length: MyLength,
rootCount: MyRootCount
}),
},
methods: {
addRoot(payload) {
this.$store.commit(RootAdd, payload);
},
...mapMutations({
add: MyAdd,
}),
...mapActions({
cut: MyCut,
cutRoot: MyCutRoot,
addTest: MyAddRoot
}),
}
}
import Vuex, {Store} from "vuex";
import Vue from "vue";
import {MyAdd, RootAdd} from "./MutationTypes";
import {MyAddRoot, MyCut, MyCutRoot} from "./ActionTypes";
import {MyCount, MyName, RootCount} from "./StateTypes";
import {MyLength, MyRootCount} from "./GetterTypes";
Vue.use(Vuex);
const myStore = {
namespaced: true,
state: {
[MyCount]: 0,
[MyName]: "Test",
},
mutations: {
[MyAdd](state, payload) {
state.count += payload;
}
},
actions: {
[MyCut](context, payload) {
context.commit("add", payload);
},
[MyCutRoot](context) {
context.commit("add", -context.rootState.rootCount);
},
[MyAddRoot](context) {
context.commit("add", 3, {root: true});
}
},
getters: {
[MyLength](state) {
return state.name.length;
},
[MyRootCount](state, getters, rootState) {
return rootState.rootCount;
},
}
}
const store = new Store({
modules: {
my: myStore
},
state: {
[RootCount]: 0
},
mutations: {
[RootAdd](state, payload) {
state.rootCount += payload;
}
},
});
export default store;
拖了幾天才把全部都玩過一輪,把內容都筆記一下
Vuex https://vuex.vuejs.org/zh/
Sample Code https://github.com/ianChen806/VuexSample