使用 Vuex 是為了當元件之間都需要共用資料時,使用一個像是公用容器來管理資料,我們把所有要共用的資料都拉進此容器中,讓所有元件都能在此容器取得或操作資料。
使用 Vuex 的好處:
Vuex 的基本架構:
關於 Vuex 的知識會比較多,因篇幅所限,以下只會簡述 Vuex 最基本的架構,並以我自己作為新手覺得較重要或常常忽略的部分去解說。
因為 Vuex 會透過 Vue 插件,把 store 實體從根元件注入到子元件裏。因此,子元件透過 this.$store
就能操作在 Vuex 裏的資料。
當你建立一個有 Vuex 的 Vue CLI 專案,在 store/index.js 就會出現以下的結構,並先利用 createStore
來建立一個 store 的實體。並 export 回到 main.js 裏使用。
import { createStore } from 'vuex'
export default createStore({
state: {
// 所有在 store 裏的資料
},
actions: {
// 負責觸發 mutations
// 可處理非同步程式(e.g: 打 API)
},
mutations: {
// 負責改變 state 裏的資料
},
getters: {
// 像 computed 一樣,運算處理 state 資料
},
modules: {
// 按需求分拆 module
// 每個 module 都有自己的state, actions, mutations, getters, modules
}
})
未使用 Vuex 之前,資料流會像是下圖描述,以 props / emit 為例,當 emit 觸發事件,就會修改資料,最後畫面會再渲染那筆更新的資料。
畫面觸發事件(View) --> emit (Actions) --> 修改資料,包括修改 props所傳遞的資料(State) --> 更新畫面 (View)
比起以上做法,Vuex 仍然會遵從單一資料流的做法,但執行過程會更嚴謹,明顯不同是:
整篇文章會以一個簡單的 todo list 為例子,但不會每個部分都作講說,只會針對看看 Vuex 裏所有函式需要注意的地方。例子如下:
https://codesandbox.io/s/vuex-vuex-todo-list-shi-fan-b6iud
Actions 作為就是觸發 mutations,在函式裏可使用 context 參數,例如以下我在例子中的寫法:
toggleComplete(context, id) {
this.commit("updateComplete", { id });
}
context 物件就是 store 所有的屬性和方法,包括 state, getter 等等:
使用 mutations 時,有兩點要先注意:
關於第二點,舉例說,以下寫法並不建議:
this.$store.commit('changeSth', 你想傳遞的資料)
這寫法是在元件裏,直接跳過 actions 來觸發 mutations。雖然這不會報錯,但是不是良好的寫法。因為在眾多元件裏,有時使用 commit,有時使用 dispatch,就會導致資料流不統一,以致程式難以維護。所以一律建議遵從官方做法,保持使用 dispatch
來觸發 actions,再由 actions 觸發 mutations。並非直接使用 commit
觸發 mutations。
另外,有一個 payload 的物件可以使用。如果我們傳送參數到 mutations 時是以物件的方式傳送,我們可以在 payload 裏找到,例如我在 store/actions.js 是以 { id }
這樣來觸發 mutations,並把此物件傳到 mutaions 裏:
TodoCard.vue:
methods: {
changeComplete(id) {
this.$store.dispatch('toggleComplete', id)
}
}
store/actions.js
toggleComplete(context, id) {
this.commit("updateComplete", { id });
}
store/mutations.js
updateComplete(state, payload) {
const target = state.todos.find((todo) => todo.id === payload.id);
target.complete = !target.complete;
}
例如,當我按下 Watch movie,把此 todo 事項的 id (即是 2),傳到 toggleComplete
actions 和 updateComplete
mutations 裏,在 mutation 再用 console.log(payload)
查看 payload 物件:
如果在 actions 裏,不用物件包起來也行。但官方建議是使用物件。有些時候我們會傳多筆資料而需要用到的物件,因此統一使用物件會比較單純。
在元件中操作 store 資料時,可使各種 map helpers 來引入 store 的各種屬性到元件裏使用。例如我在 computed
裏:
import { mapGetters } from 'vuex';
computed: {
...mapGetters({ allTodos: 'getTodos' })
}
以上寫法即等於:
computed: {
...mapGetters(['getTodos']),
allTodos() {
return this.getTodos;
}
}
mapGetters 本身是一個函式,它會回傳在 store 的 getters 裏的函式,例如你傳入了 'getTodos'
,它就回傳在 store 的 getter 裏的 getTodos
函式給你:
mapGetters(['getTodos']) // {getTodos: ƒ}
官方文件這裏提到,如果你想為 getTodos
這函式設置另一個屬性,就需要傳入物件:
mapGetters({ allTodos: 'getTodos' }) // {allTodos: ƒ}
最後,使用展開語法 ...
,把 {allTodos: ƒ}
裏的屬性和值,展開在我們例子中的 computed 裏。
除了 mapGetters,還有其他 map helpers 可以使用。使用方法也是大致相同:
import { mapGetters, mapState, mapActions, mapMutations} from 'vuex';
當專案規模較大時,可以把 store 裏的 state、getters、mutations、actions 都獨立拆成一個個檔案,再逐一引入到 store/index.js 這個主檔案中。
檔案結構:
store
index.js
getters.js
actions.js
mutations.js
state.js
store/index.js:
import { createStore } from 'vuex';
import state from './state';
import actions from './actions';
import mutations from './mutations';
import getters from './getters';
export default createStore({
state,
actions,
mutations,
getters
});
以 index/actions.js 為例,actions.js 就會放我們所有的 actions 函式:
export default {
toggleComplete(context, id) {
this.commit('updateComplete', { id });
},
...
};
另外,在大型專案裏,可以按功能需求去拆分 module,再在主檔案 store/index.js 裏的 moduleds 屬性裏引入。每個 module 就是另一個 store,裏面同樣可以有自己的 state、actions、mutations、getters、modules。
例如在示範的 todo list 例子中,我把備註欄訊息拆成一個 module:
store/Note/index.js
export default {
// 當 namespaced 是 true 時,載入這裏的資料時,必須寫 'Note/...'
namespaced: true,
state: {
note: 'Dummy text'
},
actions: {
createNote(context, note) {
context.commit('setNote', note);
}
},
mutations: {
setNote(state, note) {
state.note = note;
}
},
getters: {
getNote(state) {
return state.note;
}
}
};
回到主檔案 store/index.js,在 modules 屬性引入 Note module:
export default createStore({
state,
actions,
mutations,
getters,
modules: {
Note
}
});
並在 App.vue 裏引入,把 Dummy text
顯示出來:
App.vue:
computed: {
...mapGetters({ allTodos: 'getTodos', note: 'Note/getNote' })
},
以上示範中,我需要用 Note/getNote
來引入 Note module 裏的 note 資料。因為我在 Note/index.js 裏設定了 namespaced: true
。
設定 namespaced 好處是,在命名所有 state、actions 或 mutations 等等的資料或函式名稱時,不用擔心與主檔案 store/index.js 裏的已定義的 state、actions 等資料或函式名稱相同,以致產生衝突。同時,提高程式碼的可讀性,讓人一眼就看到此東西是存在於公用的 store,還是某個 module 裏。
由畫面觸發事件 --> 觸發 actions --> 觸發 mutations --> 修改 state --> 更新畫面
store/index.js
的 modules 屬性裏引入這些 module。2021 Vue3 專業職人 - 入門篇
LEARN VUEX IN 15 MINUTES (VUE.JS STATE MANAGEMENT) FOR 2020 // DAD JOKE GENERATOR APP -VUEX TUTORIAL
Vuex 面試題:使用 vuex 的核心概念