目錄
11.1狀態管理與應用場景
1)state
2)Getters
3)Mutations
4)Actions
5)Module
11.2Vuex的安裝與基本應用
11.3Vuex的核心概念
一句話解釋vuex:就是單獨成立一個組件,這個組件存儲共享的數據,其他組件都可以從這個共享組件里面抽取數據。這就是vuex的作用。
11.1狀態管理與應用場景
本章主要講解了Vuex的基本用法。通過本章的學習,掌握Vuex的state、getters、mutations、actions等核心概念,掌握如何使用Vuex進行狀態管理。
狀態管理,管理的是全局狀態,即全局變量。Vuex是一個專為Vue.js應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
大白話:Vuex 是一個插件,可以幫我們管理 Vue 通用的數據 (多組件共享的數據)。例如:購物車數據 個人信息數
狀態(state):驅動應用的數據源,即組件中的data;
視圖(view):以聲明方式將狀態映射到視圖,如{{counter}};
操作(action):響應在視圖上的用戶輸入導致的狀態變化,即組件的函數methods。
1)state
state是存儲的單一狀態,是存儲的基本數據。
2)Getters
getters是store的計算屬性,對state的加工,是派生出來的數據。就像computed計算屬性一樣,getter返回的值會根據它的依賴被緩存起來,且只有當它的依賴值發生改變才會被重新計算。
3)Mutations
mutations提交更改數據,使用store.commit方法更改state存儲的狀態。(mutations同步函數)
4)Actions
actions像一個裝飾器,提交mutation,而不是直接變更狀態。(actions可以包含任何異步操作)
5)Module
Module是store分割的模塊,每個模塊擁有自己的state、getters、mutations、actions。
在較大型的項目中,將有許多組件用到同一變量,比如,一個登錄的狀態,很多頁面組件都需要這個信息。在這樣的情景下,使用Vuex進行登錄狀態的統一管理就很方便。當然,雖然麻煩但也可以時刻在對應頁面操作cookie。所以,狀態管理不是必需的,所有狀態管理能做的,都能用其它方式實現,但是狀態管理提供了統一管理的地方,操作方便,也更加明確。但一些狀態只是父組件和子組件共享,不推薦使用狀態管理實現,而用$emit和props即可簡單實現。
11.2Vuex的安裝與基本應用
1.安裝Vuex
與VueRouter一樣,將Vuex添加到項目中也有4種主要方法:本地獨立版本方法、CDN方法、NPM方法以及命令行工具(VueCLI)方法。
2.項目文件中導入并顯式地使用Vuex
使用VueCLI安裝Vuex后,首先,在項目的/src/store/index.js文件中,導入Vuex模塊,并創建一個store(倉庫)。
然后,在項目主文件main.js中導入Vuex,并顯式地使用Vue實例調用Vuex。
import?{ createApp }?from?'vue'
import?App?from?'./App.vue'
import?store?from?'./store'?//導入store目錄中的index.js,Vuex的創建與配置在該文件中
createApp(App).use(store).mount('#app')
11.3Vuex的核心概念
Vuex應用的核心是store,即倉庫。store實際上就是一個容器,它包含應用中大部分的狀態(state),與單純的全局對象不同,主要有兩點區別。
(1)Vuex的狀態存儲是響應式的。也就是說,當Vue實例或組件從倉庫store中讀取狀態時,若store中狀態發生變化,那么相應的Vue實例或組件也會高效更新。
(2)用戶不能直接更新store中的狀態。更新的唯一途徑是顯式地提交mutation(類似于事件),以便跟蹤每一個狀態的變化。
一個完整的store包含state、getters、mutations、actions、modules五大組成部分。
11.3.1?Vuex中的state
Vuex使用單一狀態樹,即使用一個對象包含了所有的應用層級狀態,作為一個唯一的數據源而存在。也就是說,每個應用將僅包含一個倉庫store實例。因此,需要狀態跟蹤(管理)的數據保存在Vuex選項的state選項內。
1.在Vue?組件中通過computed計算屬性獲得?Vuex狀態
$store與store的區別
l??$store是掛載在Vue?實例上的(即Vue.prototype),而組件也是一個Vue實例。在組件中可使用this訪問原型上的屬性,template擁有組件實例的上下文,可直接通過{{$store.state?}}訪問,等價于script?中的this.$store.state。
l??store是掛載到Vue上,為Vue的根實例;store引入后被注入到Vue上,成為Vue的原型屬性,所以store是掛載到Vue上,為Vue的根實例;store引入后被注入到Vue上,成為Vue的原型屬性,所以在script中通過store.state和$store.state都可以訪問。
l??至于{{store.state}},script中的data需聲明過store才可以訪問。
2.在Vue?組件中通過mapState()輔助函數獲得Vuex?狀態
當一個組件需要獲取多個狀態時,將這些狀態都聲明為計算屬性會有些重復和冗余。為解決這個問題,Vuex通過使用?mapState()?輔助函數幫助生成計算屬性,減少按鍵次數。
mapState()?輔助函數返回的是一個對象,用來獲取多個狀態。mapState()可以接收{}或[]作為參數。
{}參數為鍵值對形式參數,即key:value,key為計算屬性,value為函數,參數為store.state,返回需要的state。
computed:?mapState({
? ??// 箭頭函數可使代碼更簡練
? ??count:?state?=>?state.count,
? ??// 傳字符串參數 'count' 等同于 `state => state.count`
? ??countAlias:?'count',
? ??// 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數
? ??countPlusLocalState(state) {
? ? ??return?state.count?+?this.localCount
? ? }
? })
當映射的計算屬性名稱與state的子節點名稱相同時,可以為mapState()傳一個字符串數組參數。
computed:?mapState([
??//?映射?this.count?為?store.state.count
??'count'? ?//可以有多個state對象中屬性,用逗號分隔
])
3.對象展開運算符
mapState()函數返回的是一個對象。如何將它與局部計算屬性混合使用呢?通常,首先需要使用一個工具函數將多個對象合并為一個,然后將最終對象傳給
computed 屬性。但自從有了對象展開運算符(…),可以極大地簡化寫法。
computed: {
? localComputed () {?/* ... */?},
??// 使用對象展開運算符將此對象混入到外部對象中
? ...mapState({
? ??// ...
? })
}
app.vue
app是從共享組件中讀取數據,這里有兩種方式,
方法一
this.$store.state.bookPress
方法二
...mapState(['BISBN',?'bookPrice',?'bookAuthor']),注意這里的三個點的含義是打散的意思,我們看看下面的這個例子,.mapState數據賦值給變量S,然后在控制臺打印,發現是個鍵值對,key是組件state里面的對象key。
let?s?=?mapState(['BISBN',?'bookPrice',?'bookAuthor'])
...mapState(['BISBN',?'bookPrice',?'bookAuthor']),最后就會變成
BISBN:??mappedState()
bookPrice:??mappedState()
bookAuthor??mappedState()
<template><div><h3>{{bookName}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>ISBN:{{$store.state.BISBN}}</h3><h3>價格:{{$store.state.bookPrice}}</h3></div><hr/><div><h3>{{bookName}}</h3><h3>作者:{{bookAuthor}}</h3><h3>出版社:{{bookPress}}</h3><h3>ISBN:{{BISBN}}</h3><h3>價格:{{bookPrice}}</h3></div>
</template>
<script>
import?{ mapState }?from?'vuex'
export?default?{name:?'App',data() {//組件中的私有數據return?{bookName :?'Vue.js '}},//使用計算屬性獲取store中的狀態數據computed: {bookPress() {return?this.$store.state.bookPress},//使用對象展開運算符獲取store中的狀態數據...mapState(['BISBN',?'bookPrice',?'bookAuthor']),}
}
</script>
<style>
#app?{font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color:?#2c3e50;margin-top:?60px;
}
</style>
index.js
import { createStore }?from?'vuex'
export default createStore
({ ?state: { BISBN :?'9787302598503', ??bookPrice :?99.8, ? ?bookAuthor :?'劉巍', ? ?bookPress :?'江西出版社'? },?mutations: { ?},?actions: { ?}, ?modules: { ?}})
main.js
import?{ createApp }?from?'vue'
import?App?from?'./App.vue'
import?store?from?'./store'
createApp(App).use(store).mount('#app')
11.3.2?Vuex中的getters
在工程項目中,有時需要從?store.state?中派生出一些狀態,如對列表進行過濾并計數,可以通過計算屬性來實現,具體代碼如下。
computed: {doneTodosCount () {return?this.$store.state.todos.filter(todo=> todo.done).length}
}
Vuex允許在store中定義“getters”(可以認為是?store?的計算屬性)。getters可以接受state?作為其第一個參數,示例代碼如下。
const?store?=?createStore({state: {todos: [{?id:?1,?text:?'...',?done:?true?},{?id:?2,?text:?'...',?done:?false?}]},getters: {doneTodos?(state) {return?state.todos.filter(todo => todo.done)}} })
index.js
import?{ createStore }?from?'vuex'
export?default?createStore({state: {BISBN?:?'9787302598503',bookPrice :?99.8,bookAuthor :?'劉巍',bookPress :?'江西出版'},getters: {//接受 state 作為其第一個參數getBookPrice(state) {return?state.bookPrice},//接受其他 getters 作為第二個參數getThreeTimesBookPrice(state, getters) {return?state.bookPrice?+ getters.getBookPrice?*?2}},mutations: {},actions: {},modules: {}
})
getters程序流程圖:
①在21行中用插值語法讀取計算屬性的值.
②計算梳理有個方法mapGetters里面有數組,數組里面就是方法名,系統就會調用store下面的index.JS文件,getters是默認的語法關鍵字.
③getters方法就是從數據源state中獲取數據,然后二次加工.
app.vue
<template><div><h3>{{bookName}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>ISBN:{{$store.state.BISBN}}</h3><h3>價格:{{$store.state.bookPrice}}</h3></div><hr/><div><h3>{{bookName}}</h3><h3>作者:{{bookAuthor}}</h3><h3>出版社:{{bookPress}}</h3><h3>ISBN:{{BISBN}}</h3><h3>價格:{{bookPrice}}</h3></div><hr/><h3>getters訪問</h3><h3>一本書花的錢:{{$store.getters.getBookPrice}}</h3><h3>三本書花的錢:{{$store.getters.getThreeTimesBookPrice}}</h3><h3>一本書花的錢:{{getBookPrice}}</h3><h3>三本書花的錢:{{getThreeTimesBookPrice}}</h3>
</template>
<script>
import?{ mapState }?from?'vuex'
import?{ mapGetters }?from?'vuex'
export?default?{name:?'App',data() {//組件中的私有數據return?{bookName :?'Vue.js 3'}},//使用計算屬性獲取store中的狀態數據computed: {bookPress() {return?this.$store.state.bookPress},//使用對象展開運算符獲取store中的狀態數據...mapState(['BISBN',?'bookPrice',?'bookAuthor']),...mapGetters(['getBookPrice',?'getThreeTimesBookPrice'])//混入計算屬性}
}
</script>
<style>
#app?{font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color:?#2c3e50;margin-top:?60px;
}
</style>
main.js
import?{ createApp }?from?'vue'
import?App?from?'./App.vue'
import?store?from?'./store'
createApp(App).use(store).mount('#app')
11.3.3?Vuex中的mutations
更改?Vuex?的?store?中的狀態的唯一方法是提交?mutations。每個?mutation?都有一個字符串的事件類型?(type)和一個回調函數?(handler)。這個回調函數就是實際進行狀態更改的地方,并且它會接受?state?作為第一個參數。(其實簡單的理解就是state的數據不能直接修改,需要通過mutations里面的方法去修改數據源state的數據)
const?store =?createStore({state: {count: 1},mutations: {increment?(state) {??// increment為事件類型type,state為參數//?變更狀態state.count++}}})
不能直接調用一個?mutation?處理函數。這個選項更像是事件注冊:“當觸發一個類型為?increment?的?mutation?時,調用此函數。”要喚醒一個?mutation?處理函數,需要以相應的?type?調用?store.commit()方法。store.commit('increment')
11.3.4?Vuex中的actions
actions?類似于?mutations,不同在于以下兩點。
l??actions提交的是?mutations,而不是直接變更狀態。
l??actions可以包含任意異步操作。
actions中的方法需要使用store.dispatch()方法調用。action函數接受一個與?store?實例具有相同方法和屬性的?context?對象,因此可以調用?context.commit()?提交一個?mutation,或者通過?context.state?和?context.getters?來獲取?state?和?getters。
一句話解釋:action就是異步請求,比如我調用一個接口,我不會等接口結果,就處理后面的代碼,等接口返回數據的時候,我重新渲染頁面。
app.vue
<template><div><h3>書名:{{bookName}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>原價:{{bookPrice}}</h3></div><hr/><my-add></my-add><hr/><my-reduce></my-reduce>
</template>
<script>
import?{ mapState }?from?'vuex'
import?AddBookPrice?from?'./components/AddBookPrice.vue'
import?ReduceBookPrice?from?'./components/ReduceBookPrice.vue'
export?default?{name:?'App',computed: {...mapState(//混入計算屬性['bookName','bookPrice'])},components: {//定義子組件'my-add':?AddBookPrice,'my-reduce':?ReduceBookPrice}
}
</script>
<style>
#app?{font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color:?#2c3e50;margin-top:?60px;
}
</style>
index.js
import?{ createStore }?from?'vuex'
export?default?createStore({state: {BISBN?:?'9787302598503',bookPrice :?99.8,bookAuthor :?'劉巍',bookPress :?'江西出版社',bookName :?'Vue.js 3'},getts: {},mutations: {addBookBy10(state) {state.bookPrice?= state.bookPrice?+?10},addBookByNum(state, num) {state.bookPrice?= state.bookPrice?+ num},reduceBookBy10(state) {state.bookPrice?= state.bookPrice?-?10},reduceBookByNum(state, num) {state.bookPrice?= state.bookPrice?- num},},actions: {//同步增加addBookBy10Action(context) {//執行mutations中的addBookBy10context.commit('addBookBy10')},//同步減少,step為參數reduceBookByNumAction(context, step) {//執行mutations中的reduceBookByNumcontext.commit('reduceBookByNum', step)},//異步增加addBookBy10ActionAsync(context) {setInterval(() =>?{context.commit('addBookBy10')},?1000);},//異步減少,step為參數reduceBookByNumActionAsync(context, step) {setInterval(() =>?{context.commit('reduceBookByNum', step)},?1000);}},modules: {}
})
main.js
import?{ createApp }?from?'vue'
import?App?from?'./App.vue'
import?store?from?'./store'
createApp(App).use(store).mount('#app')