學習筆記之Vuex的用法總結(Vue狀態(tài)管理)
一、 前言
接觸Vuex之前我們組件間共享數(shù)據(jù)的方式
- 父向子傳值: v-bind屬性綁定
- 子向父傳值: v-on 事件綁定
兄弟組件之間共享數(shù)據(jù): EventBus
- 1) $emit 發(fā)送數(shù)據(jù)的那個組件
- 2) $on 接收數(shù)據(jù)的那個組件
上面這三種共享數(shù)據(jù)方式,只適合小范圍的數(shù)據(jù)共享,如果需要頻繁的或大范圍的來實現(xiàn)數(shù)據(jù)的共享,這三種方式就有點力不從心了,這時候,Vuex誕生了!
二、初識Vuex
2.1 Vuex是什么?
Vuex是實現(xiàn)組件全局狀態(tài)(數(shù)據(jù))管理的一種機制,可以方便的實現(xiàn)組件之間數(shù)據(jù)的共享。
把數(shù)據(jù)存在store中,別的組件需要的話直接去store里取
2.2 使用Vuex統(tǒng)一管理狀態(tài)的好處
1)能夠在Vuex中集中管理共享的數(shù)據(jù),易于開發(fā)和后期維護
2)能夠高效地實現(xiàn)組件之間的數(shù)據(jù)共享,提高開發(fā)效率
3)存儲在Vuex中的數(shù)據(jù)都是響應式的,能夠實時保持數(shù)據(jù)與頁面的同步
2.3 什么樣的數(shù)據(jù)適合存儲到Vuex中?
一般情況下,只有組件之間共享的數(shù)據(jù),才有必要存儲到Vuex中;對于組件中的私有數(shù)據(jù),依舊存儲在組件自身的data中
2.4 什么時候應該用Vuex?
1)這個問題因人而異,如果你不需要開發(fā)大型的單頁應用,此時你完全沒有必要使用Vuex, 比如頁面就兩三個,使用Vuex后增加的文件比你現(xiàn)在的頁面還要多,那就沒這個必要了。
2)假如你的項目達到了中大型應用的規(guī)模,此時你很可能會考慮如何更好地在組件外部管理狀態(tài),Vuex將會成為自然而然的選擇。
2.5 Vuex基本使用
1.安裝Vuex依賴包
npm i vuex --save
2.在項目的根目錄下新增一個store文件夾,在該文件夾內(nèi)創(chuàng)建index.js
此時你的項目的src
文件夾應當是這樣的
│ App.vue │ main.js │ ├─assets │ logo.png │ ├─components │ HelloWorld.vue │ ├─router │ index.js │ └─store index.js
3) 初始化store
下index.js
中的內(nèi)容
import Vue from 'vue'; //首先引入vue import Vuex from 'vuex'; //引入vuex Vue.use(Vuex) export default new Vuex.Store({ state: { // state 類似 data //這里面寫入數(shù)據(jù) }, getters:{ // getters 類似 computed // 在這里面寫個方法 }, mutations:{ // mutations 類似 methods // 寫方法對數(shù)據(jù)做出更改(同步操作) }, actions:{ // actions 類似 methods // 寫方法對數(shù)據(jù)做出更改(異步操作) } }) //可能有的地方書寫的風格不是這樣的,如果需要的了解的可以百度看看其他人的
4)main.js
中將store掛載到當前項目的Vue實例當中去
在main.js中使用我們的index.js(這里是為了防止在各個組件中引用,因為main.js中,有我們的new Vue 實例?。。?/p>
//main.js import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, //store:store 和router一樣,將我們創(chuàng)建的Vuex實例掛載到這個vue實例中;所有的組件,可以直接從 store 中獲取全局數(shù)據(jù)了 render: h => h(App) })
5)最后修改App.vue:
<template> <div id='app'> name: <h1>{{ $store.state.count}}</h1> </div> </template>
或者在組件方法中使用
..., methods:{ add(){ //使用this.$store.state.xxx可直訪問到倉庫中的狀態(tài) console.log(this.$store.state.count) } }, ...
注意,不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation。
這樣使得我們可以方便的跟蹤每一個狀態(tài)的變化。
三、VueX中的核心內(nèi)容
Vuex中的主要核心概念如下:
state
存放狀態(tài)getters
加工state成員給外界mutations
state成員同步操作actions
異步操作modules
模塊化狀態(tài)管理
3.1 state
state 提供唯一的公共數(shù)據(jù)源,所有共享的數(shù)據(jù)都要統(tǒng)一放到store的state中進行存儲。
//創(chuàng)建store數(shù)據(jù)源,提供唯一公共數(shù)據(jù) const store = new Vuex.Store({ state: { count: 0 },
3.1.1 組件訪問state數(shù)據(jù)的兩種方式 組件訪問state中數(shù)據(jù)的
第一種方式:
// vue模板中不要使用this this.$store.state.全局數(shù)據(jù)名稱
組件訪問state中數(shù)據(jù)的
第二種方式:
// 1. 從 vuex 中按需導入 mapState 函數(shù) import { mapState } from 'vuex'
通過剛才導入的 mapState 函數(shù),將當前組件需要的全局數(shù)據(jù),映射為當前組件的
computed 計算屬性:
// 2. 將全局數(shù)據(jù),映射為當前組件的計算屬性 computed:{ ...mapState (['count']) //如果使用的名稱和index.js中的一樣,直接寫成上面數(shù)組的形式就行, // 如果想改變下名字,寫法如下 ...mapState({ newCount: state => state.count })
三個點…是展開運算符,意思是把全局里面的數(shù)據(jù)映射為當前組件的計算屬性,在使用全局數(shù)據(jù)的時候,就像用一個計算屬性一樣簡單;可認為當前count就是一個計算屬性,希望將計算屬性的值顯示在頁面上。
3.2 getters
getter 用于對 store 中的數(shù)據(jù)進行加工處理形成的數(shù)據(jù)。
1)getter 類似 Vue的計算屬性;它的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算。
2)store 中數(shù)據(jù)發(fā)生變化,getter的數(shù)據(jù)也會跟著變化。
3.2.1 getters基本使用
getters中的方法有兩個默認參數(shù)
1)state 永遠都是自身的state,state代表全局的數(shù)據(jù)對象;
2)getters 當前getters對象,用于將getters下的其他getter拿來用
例如
const store = new Vuex.Store({ state: { count: 0 }, getters:{ showNum(state){ return "當前最新的數(shù)量是【'+ state.count +'】" }, fullNum(state,getters){ return getters.showNum +'增加1:'+ state.count++ } }
//組件中調用 this.$store.getters.fullNum
官方建議:
是不是每次都寫this.$store.getters.XXX讓你感到厭煩,你實在不想寫這個東西怎么辦,官方建議我們可以使用mapGetters
去解構到計算屬性中,就像使用mapState一樣,就可以直接使用this調用了,就像下面
3.2.2 方法2 這樣:
3.2.2 使用getters 的兩種方式
1.使用 getters 的第一種方式:
this.$store.getters.名稱
2.使用 getters 的第二種方式:
import { mapGetters } from 'vuex' computed:{ ...mapGetters (['fullNum'])
//組件中使用 跟計算屬性一樣調用 <template> <div> <h2>當前最新的count值為{{ fullNum }}</h2> </div> </template>
3.3 Mutations
3.3.1 為什么用Mutations??
用 `this.$store.state.count` 這種方式,不利于我們知道到底是誰修改了store全局數(shù)據(jù), 不利于后期維護;
如果是用mutations修改,有問題可直接找mutations,找到對應的mutations就能找到問題了,方便后期維護;
通過這種方式雖然操作起來稍微繁瑣些,但是可以集中監(jiān)控所有數(shù)據(jù)的變化。
3.3.2 Mutations基本使用
mutations
方法都有默認的形參:
([state] ,[payload])
1) state 必傳的默認參數(shù);永遠都是自身的state,state代表全局的數(shù)據(jù)對象;
2)payload 載荷;是該方法在被調用時傳遞額外參數(shù)使用的
3.3.3 觸發(fā)mutations時候攜帶參數(shù)
在大多數(shù)情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 0 }, mutations: { addCount(state) { state.count = 5; }, addCountIsWhat(state, payload) { // 增加一個帶參數(shù)的mutations方法,并且官方建議payload為一個對象 state.count = payload.count; }, }, }); export default store;
組件中使用:
<script> export default { mounted() { this.$store.commit('addCountIsWhat', {count:10}); // 調用的時候也需要傳遞一個對象 } } </script>
3.3.4 使用mutations的兩種方式
1.使用 mutations 的第一種方式:
this.$store.commit('mutations 中的方法名')
2.使用 mutations 的第二種方式:
<script> import { mapMutations } from 'vuex'; export default { mounted() { this.addCountIsWhat({count:20}); }, methods: { // 注意,mapMutations是解構到methods里面的,而不是計算屬性了 ...mapMutations(['addCountIsWhat']), }, } </script>
3.3.5 Mutation 需遵守 Vue 的響應規(guī)則
既然 Vuex 的 store 中的狀態(tài)是響應式的,那么當我們變更狀態(tài)時,監(jiān)視狀態(tài)的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:
1) 最好提前在你的 store 中初始化好所有所需屬性。
2) 當需要在對象上添加新屬性時,你應該使用 Vue.set(obj, 'newProp', 123);或者 以新對象替換老對象。例如,利用對象展開運算符 我們可以這樣寫 state.obj = { ...state.obj, newProp: 123 }
3) Vue.delete 刪除成員 Vue.delete(obj,'newProp')
3.4 Actions
Action 用于處理異步任務。
如果通過異步操作變更數(shù)據(jù),必須通過 Action,而不能使用Mutation,但是 Action 中還是要通過觸發(fā) Mutation的方式間接變更數(shù)據(jù)。
只有通過actions => mumations => state
,這個流程進行數(shù)據(jù)變更操作。
3.4.1 Actions基本使用
Actions
方法都有默認的形參:
1) context 上下文對象(相當于一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。);
2)payload 是該方法在被調用時額外傳遞參數(shù)使用的
例如:
export default new Vuex.Store({ state: { count: 0 }, //只有 mutations 中定義的函數(shù),才有權利修改 state 中的數(shù)據(jù) mutations: { addCountIsWhat(state,payload){ state.count = payload.count } }, actions: { setCount(context,payload){ //默認第一個參數(shù)是context,其值是復制的一份store setTimeout(()=>{ context.commit('addCountIsWhat',payload) },1000) } })
組件中調用:
this.$store.dispath('setCount',{count:300})
實踐中,我們會經(jīng)常用到 ES2015 的 參數(shù)解構來簡化代碼(特別是我們需要調用 commit 很多次的時候):
actions: { setCount({ commit },payload) { commit('addCountIsWhat',payload) } }
改進:
1.由于是異步操作,所以我們可以為我們的異步操作封裝為一個Promise
對象
actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('addCountIsWhat') resolve() }, 1000) }) } }
3.4.2 Action處理異步的正確使用方式
想要使用action處理異步工作很簡單,只需要將異步操作放到action中執(zhí)行(如上面代碼中的setTimeout)。
要想在異步操作完成后繼續(xù)進行相應的流程操作,有兩種方式:
1. store.dispatch返回相應action的執(zhí)行結果,而action的處理函數(shù)返回的就是Promise,所以store.dispatch仍然返回一個Promise
。
store.dispatch('actionA').then(() => { // ... })
現(xiàn)在可以寫成:
store.dispatch('actionA').then(() => { // ... })
在另外一個 action 中也可以:
actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('addCountIsWhat') }) } }
2. 利用 async/await 進行組合action。代碼更加簡潔。
// 假設 getData() 和 getOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
一個 store.dispatch 在不同模塊中可以觸發(fā)多個 action 函數(shù)。在這種情況下,只有當所有觸發(fā)函數(shù)完成后,返回的 Promise 才會執(zhí)行。
3.4.3 使用 Actions 的兩種方式
1.使用 Actions 的第一種方式:
this.$store.dispath('Actions 中的方法名')
2.使用 Actions 的第二種方式:
import { mapActions } from 'vuex' methods:{ ...mapActions (['addAsync','addNasync'])
Action部分個人覺得文檔講解的挺不錯,我剛接觸也能看的懂,可參考下這部分:
https://vuex.vuejs.org/zh/guide/actions.html#%E7%BB%84%E5%90%88-action
3.4.4 組件中直接調用映射的methods方法
3.5 modules
modules
,可以讓每一個模塊擁有自己的state
、mutation
、action
、getters
,使得結構非常清晰,方便管理;如果所有的狀態(tài)或者方法都寫在一個store
里面,將會變得非常臃腫,難以維護。
3.5.1 怎么用module?
一般結構:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB})
模塊內(nèi)部的數(shù)據(jù):
1) 模塊內(nèi)部的 state 是局部的,是被限制到模塊的命名空間下,需要命名空間才能訪問,也就是模塊私有的比如 moduleA.js 模塊 state 中的 count 數(shù)據(jù),我們要通過 this.$store.state.moduleA.count 獲取。
2)但actions 和mutations, 其實還有 getters 卻沒有被限制,在默認情況下,它們是注冊到全局命名空間下的,所謂的注冊到全局命名空間下,
其實就是我們訪問它們的方式和原來沒有module 的時候是一樣的。
比如沒有 module 的時候,this.$store.dispatch(“actions”);現(xiàn)在有了modules, actions 也寫在了module 下面,
我們?nèi)匀豢梢赃@么寫,this.$store.dispatch(“changeName”);組件中的getters,也是通過this.$store.getters.xxx來獲取
注意,這個時候我們寫$store.getters的時候,就不用寫成$store.getters.a.fullNum了;
因為程序會默認先從初始的store中的getters尋找有沒有fullNum這個方法,如果沒有,就會去新的模塊moduleA中尋找;
這就意味著,在開發(fā)時,一定不要寫重復名字的方法
結合案例學習下
1.在src 目錄下新建一個store文件夾,在里面建module文件夾 =》login.js,用于存放login 模塊的狀態(tài)。 為了簡單起見,把模塊下的state, actions,mutations, getters 全放在login.js文件中。
先簡單給它增加一個狀態(tài),userName: “sam”
const state = { useName: "sam" }; const mutations = { }; const actions = { }; const getters = { }; // 不要忘記把state, mutations等暴露出去。 export default { state, mutations, actions, getters }
2.在store文件夾下,新建一個index.js作為根store,他通過modules屬性引入 login 模塊。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); // 引入login 模塊 import login from "./login" export default new Vuex.Store({ // 通過modules屬性引入login 模塊。 modules: { login: login } })
3.在main.js中引入store, 并注入到vue 根實例中。
import Vue from 'vue' import App from './App.vue' // 引入store import store from "./store" new Vue({ el: '#app', store, // 注入到根實例中。 render: h => h(App) })
4.在組件中通過computed屬性獲取到login下的state. 這里要注意,在沒有modules 的情況下,
組件中通過 this.store.state.屬性名,有了 modules 之后,state被限制到login的命名空間下,所有屬性名前必須加命名空間,在這里是 this.$store.state.login.userName
<template> <div id="app"> <img src="./assets/logo.png"> <h1>{{useName}}</h1> </div> </template> <script> export default { // computed屬性,從store 中獲取狀態(tài)state,不要忘記login命名空間。 computed: { useName: function() { return this.$store.state.login.useName } } } </script>
項目目錄如下:
5.通過actions, mutations 改變名字, 這就涉及到dispatch action, commit mutations, mutations 改變state.
先在 modules 文件夾 login.js
中添加changeName action 和 change_name mutations.
const mutations = { change_name (state, anotherName) { state.useName = anotherName; } }; const actions = { changeName ({commit},anotherName) { commit("change_name", anotherName) } };
在組件 中添加一個按鈕:<button> change to json</button>
, 點擊時,dispatch 一個 action. 那在組件中怎么dispatch actions 呢?
<template> <div id="app"> <img src="./assets/logo.png"> <h1>{{useName}}</h1> <!-- 添加按鈕 --> <div> <button @click="changeName"> change to json</button> </div> </div> </template> <script> export default { // computed屬性,從store 中獲取狀態(tài)state,不要忘記login命名空間。 computed: { useName: function() { return this.$store.state.login.useName } }, methods: { // 和沒有modules的時候一樣,同樣的方式dispatch action changeName() { this.$store.dispatch("changeName", "Jason") } } } </script>
6.局部參數(shù)
雖然 dispatch action和 commit mutations 可以全局使用,但是寫在module 中的actions, mutations 和getters, 它們獲得的默認參數(shù)卻不是全局的,都是局部的,被限定在它們所在的模塊中的。
比如mutations和getters 會獲得state 作為第一個默認參數(shù),這個state參數(shù),就是限定在 mutations 和 getters 所在模塊的state對象,login.js 文件下的 mutations 和 getters 只會獲取到當前l(fā)ogin.js 中的 state 作為參數(shù) 。
actions 會獲得一個context 對象作為參數(shù),這個context 對象就是當前module 的實例,module 相當于一個小store.
那么怎樣才能獲取到根store 中的state 和 getters 呢? Vuex 提供了 rootState, rootGetters 作為module 中 getters 中默認參數(shù), actions中context 對象,也會多了兩個屬性,context.getters, context. rootState, 這些全局的默認參數(shù),都排在局部參數(shù)的后面。
我們在index.js中添加 state, getters:
export default new Vuex.Store({ // 通過modules屬性引入login 模塊。 modules: { login: login }, // 新增state, getters state: { job: "web" }, getters: { jobTitle (state){ return state.job + "developer" } } })
store目錄下的login.js組件
const mutations = { change_name (state, anotherName) { state.useName = anotherName; } }; const actions = { // actions 中的context參數(shù)對象多了 rootState 參數(shù) changeName ({commit, rootState},anotherName) { if(rootState.job =="web") { commit("change_name", anotherName) } } }; const getters = { // getters 獲取到 rootState, rootGetters 作為參數(shù)。 // rootState和 rootGetter參數(shù)順序不要寫反,一定是state在前,getter在后面,這是vuex的默認參數(shù)傳遞順序, 可以打印出來看一下。 localJobTitle (state,getters,rootState,rootGetters) { console.log(rootState); console.log(rootGetters); return rootGetters.jobTitle + " aka " + rootState.job } };
7.如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true
的方式使其成為帶命名空間的模塊。
當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調整命名。
例如:
const state = { useName: "sam" }; const mutations = { change_name (state, anotherName) { state.useName = anotherName; } }; const actions = { changeName ({commit, rootState},anotherName) { if(rootState.job =="web") { commit("change_name", anotherName) } }, alertName({state}) { alert(state.useName) } }; const getters = { localJobTitle (state,getters,rootState,rootGetters) { return rootGetters.jobTitle + " aka " + rootState.job } }; // namespaced 屬性,限定命名空間 export default { namespaced:true, state, mutations, actions, getters }
當所有的actions, mutations, getters 都被限定到模塊的命名空間下,我們dispatch actions, commit mutations 都需要用到命名空間。
如 dispacth(“changeName”), 就要變成 dispatch("login/changeName"); getters.localJobTitle 就要變成 getters["login/localJobTitle"]
要使用的頁面中這樣調用 如下:
<template> <div id="app"> <img src="./assets/logo.png"> <h1 @click ="alertName">{{useName}}</h1> <!-- 增加h2 展示 localJobTitle --> <h2>{{localJobTitle}}</h2> <!-- 添加按鈕 --> <div> <button @click="changeName"> change to json</button> </div> </div> </template> <script> import {mapActions, mapState,mapGetters} from "vuex"; export default { // computed屬性,從store 中獲取狀態(tài)state,不要忘記login命名空間。 computed: { ...mapState("login",{ useName: state => state.useName }), localJobTitle() { return this.$store.getters["login/localJobTitle"] } }, methods: { changeName() { this.$store.dispatch("login/changeName", "Jason") }, alertName() { this.$store.dispatch("login/alertName") } } } </script>
有了命名空間之后,mapState, mapGetters, mapActions 函數(shù)也都有了一個參數(shù),用于限定命名空間,第二個參數(shù)對象或數(shù)組中的屬性,都映射到了當前命名空間中。
<script> import {mapActions, mapState,mapGetters} from "vuex"; export default { computed: { // 對象中的state 和數(shù)組中的localJobTitle 都是和login中的參數(shù)一一對應。 ...mapState("login",{ useName: state => state.useName }), ...mapGetters("login", ["localJobTitle"]) }, methods: { changeName() { this.$store.dispatch("login/changeName", "Jason") }, ...mapActions('login', ['alertName']) } } </script>
總結的也不是很透徹,只是學了些基本的,還得多刷幾遍,多上手做項目。
項目中也會用到組件間其他傳值方法,感興趣的請移步看我另一篇文章
uniapp和vue組件之間的傳值方法(父子傳值,兄弟傳值,跨級傳值,vuex)
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
vue3+ts中ref與reactive指定類型實現(xiàn)示例
這篇文章主要為大家介紹了vue3+ts中ref及reactive如何指定類型的實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06vue如何使用html2canvas和JsPDF導出pdf組件
這篇文章主要介紹了vue如何使用html2canvas和JsPDF導出pdf組件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-09-09在Vue中使用CSS3實現(xiàn)內(nèi)容無縫滾動的示例代碼
這篇文章主要介紹了在Vue中使用CSS3實現(xiàn)內(nèi)容無縫滾動的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11