1.什么是狀態管理
在開發中,我們會的應用程序需要處理各種各樣的數據,這些數據需要保存在我們應用程序的某個位置,對于這些數據的管理我們就稱之為狀態管理。
在之前我們如何管理自己的狀態呢?
- 在Vue開發中,我們使用組件化的開發方式;
- 在組件中我們定義data或者在setup中返回使用的數據,這些數據我們稱之為state;
- 在模塊template中我們可以使用這些數據,模塊最終會被渲染成DOM,我們稱之為View;
- 在模塊中我們會產生一些行為事件,處理這些行為事件時,有可能會修改state,這些行為我們稱之為actions;
2.Vuex的狀態管理
管理不斷變化的state本身也是非常困難的:
- 狀態之間相互會存在依賴,一個狀態的變化會引起另一個狀態的變化,View頁面也有可能引起狀態的變化;
- 當應用程序復雜時,state在什么時候,因為什么原因發生了變化,發生了怎么樣的變化,會變得非常難以控制和追蹤;
因此,我們是否可以考慮將組件的內部狀態抽離出來,以一個全局單例的方式來管理呢? - 在這種模式下,我們的組件樹構成了一個巨大的 “試圖View”;
- 不管在樹的那個位置,任何組件都能獲取狀態或者觸發行為;
- 通過定義和隔離狀態管理中的各個概念,并通過強制性的規則來維護視圖和狀態間的獨立性,我們的代碼便會變得更加結構化和易于維護,跟蹤;
這就是Vuex背后的基本思想,它借鑒了Flux,Redux,Elm(純函數語言,redux有借鑒它的思想);
當然,目前Vue官網也在推薦使用Pinia進行狀態管理,我后續也會進行學習。
3.Vuex的狀態管理
4.Vuex的安裝
npm install vuex
5.Vuex的使用
在src目錄下新建store目錄,store目錄下新建index.js,內容如下
import { createStore } from "vuex";const store = createStore({state:() => ({counter:100})
})export default store
在main.js中引用
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'createApp(App).use(store).mount('#app')
App.vue中使用
<template><div class="app"><h2>App當前計數:{{ $store.state.counter }}</h2><HomeCom></HomeCom> </div>
</template><script setup>import HomeCom from './views/HomeCom.vue'
</script><style>
</style>
6.創建Store
每一個Vuex應用的核心就是store(倉庫):
- store本質上是一個容器,它包含著你的應用中大部分的狀態(state);
Vuex和單純的全局對象有什么區別呢?
- Vuex的狀態存儲是響應式的
- 當Vue組件從store中讀取狀態的時候,若store中的狀態發生變化,那么相應的組件也會被更新;
- 你不能直接改變store中的狀態
- 改變store中的狀態的唯一途徑就是顯示提交(commit)mutation;
- 這樣使得我們可以方便的跟蹤每一個狀態的變化,從而讓我們能夠通過一些工具幫助我們更好的管理應用的狀態;
使用步驟:
- 創建Store對象;
- 在app中通過插件安裝;
HomeCom.vue
<template><div><h2>Home當前計數:{{ $store.state.counter }}</h2><button @click="increment">+1</button></div>
</template><script setup>import { useStore } from 'vuex';const store = useStore()function increment(){// store.state.counter++store.commit("increment")}
</script><style scoped></style>
store/index.js
import { createStore } from "vuex";const store = createStore({state:() => ({counter:100}),mutations:{increment(state){state.counter++}}
})export default store
7.在computed中使用Vuex
options-api
<h2>Computed當前計數:{{ storeCounter }}</h2>
<script>export default{computed:{storeCounter(){return this.$store.state.counter}}}
</script>
Componsition-API
<h2>Componsition-API中Computed當前計數:{{ counter }}</h2>
const store = useStore()// const setupCounter = store.state.counter; // 不是響應式const { counter } = toRefs(store.state);
8.mapState函數
options-api中使用
<!-- 普通使用 --><div>name:{{ $store.state.name }}</div><div>level:{{ $store.state.level }}</div><!-- mapState數組方式 --><div>name:{{ name }}</div><div>level:{{ level }}</div><!-- mapState對象方式 --><div>name:{{ sName }}</div><div>level:{{ sLevel }}</div>
<script>import { mapState } from 'vuex';export default {computed:{fullname(){return 'xxx'},...mapState(["name","level"]),...mapState({sName:state => state.name,sLevel:state => state.level})}}
</script>
Componsition-API
<!-- Setup中 mapState對象方式 --><!-- <div>name:{{ cName }}</div><div>level:{{ cLevel }}</div> --><!-- Setup中 使用useState --><div>name:{{ name }}</div><div>level:{{ level }}</div><button @click="incrementLevel">修改level</button>
<script setup>// import { computed } from 'vue';// import { mapState,useStore } from 'vuex';import { useStore } from 'vuex';
// import useState from '../hooks/useState'
import { toRefs } from 'vue';// 1.一步步完成// const { name,level } = mapState(["name","level"])// const store = useStore()// const cName = computed(name.bind({ $store:store }))// const cLevel = computed(level.bind({ $store:store }))// 2. 使用useState// const { name,level } = useState(["name","level"])// 3.直接對store.state進行結構(推薦)const store = useStore()const { name,level } = toRefs(store.state)function incrementLevel(){store.state.level++}
</script>
hooks/useState.js
import { computed } from "vue";
import { useStore,mapState } from "vuex";export default function useState(mapper){const store = useStore()const stateFnsObj = mapState(mapper)const newState = {}Object.keys(stateFnsObj).forEach(key=>{newState[key] = computed(stateFnsObj[key].bind({$store:store}))})return newState
}
9.getters的基本使用
某些屬性可能需要經過變化后來使用,這個時候可以使用getters:
import { createStore } from "vuex";const store = createStore({state:() => ({counter:100,name:'why',level:10,users:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},]}),mutations:{increment(state){state.counter++}},getters:{doubleCounter(state){return state.counter * 2},totalAge(state){return state.users.reduce((preValue,item)=>{return preValue + item.age},0)},message(state){return `name:${state.name} level:${state.level}`}}
})export default store
獲取
<template><div><button @click="incrementLevel">修改level</button><h2>doubleCounter:{{ $store.getters.doubleCounter }}</h2><h2>usertotalAge:{{ $store.getters.totalAge }}</h2><h2>message:{{ $store.getters.message }}</h2></div>
</template><script>export default {}
</script>
<script setup></script><style scoped></style>
注意,getter是可以返回函數的
// 獲取某一個frends,是可以返回函數的getFriendById(state){return (id) => {const friend = state.friends.find(item=>item.id == id)return friend;}}
使用
<h2>friend-111:{{ $store.getters.getFriendById(111) }}</h2>
mapGetters的輔助函數
我們可以使用mapGetters的輔助函數
options api用法
<script>import { mapGetters } from 'vuex';export default { computed:{// 數組語法// ...mapGetters(["doubleCounter","totalAge","message"]),// 對象語法...mapGetters({doubleCounter:"doubleCounter",totalAge:"totalAge",message:"message"}),...mapGetters(["getFriendById"])}}
</script>
**Composition-API中使用mapGetters **
<script setup>import { toRefs } from 'vue';// computedimport { useStore } from 'vuex';// mapGettersconst store = useStore();// 方式一
// const { message:messageFn } = mapGetters(["message"])
// const message = computed(messageFn.bind({ $store:store }))
// 方式二
const { message } = toRefs(store.getters)
function changeAge(){store.state.name = "coder why"
}
// 3.針對某一個getters屬性使用computed
const message = computed(()=> store.getters.message)
function changeAge(){store.state.name = "coder why"
}
</script>
10. Mutation基本使用
更改Vuex的store中的狀態的唯一方法是提交mutation:
mutations:{increment(state){state.counter++},decrement(state){state.counter--}
}
使用示例
store/index.js
import { createStore } from "vuex";const store = createStore({state:() => ({counter:100,name:'why',level:10,users:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},],friends:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},]}),getters:{doubleCounter(state){return state.counter * 2},totalAge(state){return state.users.reduce((preValue,item)=>{return preValue + item.age},0)},message(state){return `name:${state.name} level:${state.level}`},// 獲取某一個frends,是可以返回函數的getFriendById(state){return (id) => {const friend = state.friends.find(item=>item.id == id)return friend;}}},mutations: {increment(state){state.counter++},changeName(state){state.name = "王小波"},changeLevel(state){state.level++},changeInfo(state,userInfo){state.name = userInfo.name;state.level = userInfo.level}},
})export default store
<template><div><button @click="changeName">修改name</button><h2>Store Name:{{ $store.state.name }}</h2><button @click="changeLevel">修改level</button><h2>Store Level:{{ $store.state.level }}</h2><button @click="changeInfo">修改level和name</button></div>
</template><script>export default {methods:{changeName(){console.log("changeName")// // 不符合規范// this.$store.state.name = "李銀河"this.$store.commit("changeName")},changeLevel(){this.$store.commit("changeLevel")},changeInfo(){this.$store.commit("changeInfo",{name:'張三',level:'100'});}}}
</script><style scoped></style>
Mutation常量類型
1.定義常量 store/mutation-type.js
export const CHANGE_INFO = "CHANGE_INFO"
2.定義mutation
引入 store/index.js
import { CHANGE_INFO } from "./mutation_types";
[CHANGE_INFO](state,userInfo){state.name = userInfo.name;state.level = userInfo.level}
3.提交mutation
引入 HomeCom.vue
import {CHANGE_INFO } from "@/store/mutation_types"
changeInfo(){this.$store.commit(CHANGE_INFO,{name:'張三',level:'100'});}
10.mapMutations的使用
- 在options-api中
<template><div><button @click="changeName">修改name</button><h2>Store Name:{{ $store.state.name }}</h2><button @click="changeLevel">修改level</button><h2>Store Level:{{ $store.state.level }}</h2><button @click="changeInfo({name:'張三',level:'1999'})">修改level和name</button></div>
</template> <script>import { mapMutations } from "vuex";import {CHANGE_INFO } from "@/store/mutation_types"export default {computed:{},methods:{btnClick(){console.log("btnClick")},...mapMutations(["changeName","changeLevel",CHANGE_INFO])}}
</script> <style scoped></style>
- composition-api中
<template><div><button @click="changeName">修改name</button><h2>Store Name:{{ $store.state.name }}</h2><button @click="changeLevel">修改level</button><h2>Store Level:{{ $store.state.level }}</h2><button @click="changeInfo({name:'張三',level:'1999'})">修改level和name</button></div>
</template> <script setup>import { mapMutations,useStore } from 'vuex';import { CHANGE_INFO } from "@/store/mutation_types"const store = useStore();// 1.手動映射和綁定const mutations = mapMutations(["changeName","changeLevel",CHANGE_INFO])const newMutations = {}Object.keys(mutations).forEach(key => {newMutations[key] = mutations[key].bind({$store:store})}) const { changeName,changeLevel,changeInfo } = newMutations
</script><style scoped></style>
mutation重要原則
1.一條重要的原則就是要記住mutation必須是同步函數
- 這是因為devtool工具會記錄mutation的日記
- 每一條mutation被記錄,devtools都需要捕捉到前一狀態和后一狀態的快照
- 但是在mutation中執行異步操作,就無法追蹤到數據的變化
11. Actions的基本使用
<template><div><h2>當前計數:{{ $store.state.counter }}</h2><button @click="actionBtnClick">發起action</button><h2>當前計數:{{ $store.state.name }}</h2><button @click="actionchangeName">發起action修改name</button></div>
</template> <script>export default {computed:{},methods:{actionBtnClick(){this.$store.dispatch("incrementAction")},actionchangeName(){this.$store.dispatch("changeNameAction","bbb")}}}
</script>
<script setup></script><style scoped></style>
mapActions的使用
componets-api和options-api的使用
<template><div><h2>當前計數:{{ $store.state.counter }}</h2><button @click="incrementAction">發起action</button><h2>當前計數:{{ $store.state.name }}</h2><button @click="changeNameAction('bbbccc')">發起action修改name</button><button @click="increment">increment按鈕</button></div>
</template> <!-- <script>import { mapActions } from 'vuex';export default {computed:{},methods:{...mapActions(["incrementAction","changeNameAction"])}}
</script> -->
<script setup>import { useStore,mapActions } from 'vuex';const store = useStore();const actions = mapActions(["incrementAction","changeNameAction"]);const newActions = {}Object.keys(actions).forEach(key => {newActions[key] = actions[key].bind({$store:store})})const {incrementAction,changeNameAction} = newActions;// 2.使用默認的做法// import { useStore } from 'vuex';// const store = useStore();// function increment(){// store.dispatch("incrementAction")// }
</script><style scoped></style>
** actions發起網絡請求**
- store/index.js文件
import { createStore } from "vuex";
import { CHANGE_INFO } from "./mutation_types";
const store = createStore({state:() => ({counter:100,name:'why',level:10,users:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},],friends:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},],// // 服務器數據banners:[],recommends:[]}),getters:{doubleCounter(state){return state.counter * 2},totalAge(state){return state.users.reduce((preValue,item)=>{return preValue + item.age},0)},message(state){return `name:${state.name} level:${state.level}`},// 獲取某一個frends,是可以返回函數的getFriendById(state){return (id) => {const friend = state.friends.find(item=>item.id == id)return friend;}}},mutations: {increment(state){state.counter++},changeName(state){state.name = "王小波"},changename(state,name){state.name = name},changeLevel(state){state.level++},// changeInfo(state,userInfo){// state.name = userInfo.name;// state.level = userInfo.level// } [CHANGE_INFO](state,userInfo){state.name = userInfo.name;state.level = userInfo.level},changeBanners(state,banners){state.banners = banners},changeRecommends(state,recommends){state.recommends = recommends}},actions:{incrementAction(context){// console.log(context.commit) // 用于提交mutation// console.log(context.getters) // getters// console.log(context.state) // statecontext.commit("increment")},changeNameAction(context,payload){context.commit("changename",payload)},async fetchHomeMultidataAction(context){// // 1.返回promise,給promise設置then// // fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// // return res.json().then(data=>{// // console.log(data)// // })// // })// // 2.promisel鏈式調用 // // fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// // return res.json()// // }).then(data =>{// // console.log(data)// // })// 3.await/async const res = await fetch("http://123.207.32.32:8000/home/multidata")const data = await res.json();console.log(data);// 修改state數據context.commit("changeBanners",data.data.banner.list)context.commit("changeRecommends",data.data.recommend.list)return 'aaaa';// // return new Promise(async (resolve,reject)=>{// // const res = await fetch("http://123.207.32.32:8000/home/multidata")// // const data = await res.json();// // console.log(data);// // // 修改state數據// // context.commit("changeBanners",data.data.banner.list)// // context.commit("changeRecommends",data.data.recommend.list)// // // reject()// // resolve("aaaa")// // })// }}
})export default store
- HomeCom.vue
<template><div><h2>Home Page</h2><ul><template v-for="item in $store.state.home.banners" :key="item.acm"><li>{{ item.title }}</li></template></ul></div>
</template> <script setup>import { useStore } from 'vuex';// 進行vuex網絡請求const store = useStore()store.dispatch("fetchHomeMultidataAction").then(res=>{console.log("home中的then被回調:",res)})
</script><style scoped></style>
module的基本使用
- store/index.js文件
import { createStore } from "vuex";
import { CHANGE_INFO } from "./mutation_types";
import homeModule from './modules/home'
const store = createStore({state:() => ({counter:100,name:'why',level:10,users:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},],friends:[{id:111,name:'why',age:20},{id:112,name:'kobe',age:30},{id:113,name:'james',age:25},],// // 服務器數據// banners:[],// recommends:[]}),getters:{doubleCounter(state){return state.counter * 2},totalAge(state){return state.users.reduce((preValue,item)=>{return preValue + item.age},0)},message(state){return `name:${state.name} level:${state.level}`},// 獲取某一個frends,是可以返回函數的getFriendById(state){return (id) => {const friend = state.friends.find(item=>item.id == id)return friend;}}},mutations: {increment(state){state.counter++},changeName(state){state.name = "王小波"},changename(state,name){state.name = name},changeLevel(state){state.level++},// changeInfo(state,userInfo){// state.name = userInfo.name;// state.level = userInfo.level// } [CHANGE_INFO](state,userInfo){state.name = userInfo.name;state.level = userInfo.level},// changeBanners(state,banners){// state.banners = banners// },// changeRecommends(state,recommends){// state.recommends = recommends// }},actions:{incrementAction(context){// console.log(context.commit) // 用于提交mutation// console.log(context.getters) // getters// console.log(context.state) // statecontext.commit("increment")},changeNameAction(context,payload){context.commit("changename",payload)},// async fetchHomeMultidataAction(context){// // 1.返回promise,給promise設置then// // fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// // return res.json().then(data=>{// // console.log(data)// // })// // })// // 2.promisel鏈式調用 // // fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// // return res.json()// // }).then(data =>{// // console.log(data)// // })// // 3.await/async // const res = await fetch("http://123.207.32.32:8000/home/multidata")// const data = await res.json();// console.log(data);// // 修改state數據// context.commit("changeBanners",data.data.banner.list)// context.commit("changeRecommends",data.data.recommend.list)// return 'aaaa';// // return new Promise(async (resolve,reject)=>{// // const res = await fetch("http://123.207.32.32:8000/home/multidata")// // const data = await res.json();// // console.log(data);// // // 修改state數據// // context.commit("changeBanners",data.data.banner.list)// // context.commit("changeRecommends",data.data.recommend.list)// // // reject()// // resolve("aaaa")// // })// }},modules:{home:homeModule}
})export default store
- modules/home.js
export default{state:()=>({// 服務器數據banners:[],recommends:[]}),mutations:{changeBanners(state,banners){state.banners = banners},changeRecommends(state,recommends){state.recommends = recommends}},actions:{async fetchHomeMultidataAction(context){// 1.返回promise,給promise設置then// fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// return res.json().then(data=>{// console.log(data)// })// })// 2.promisel鏈式調用 // fetch("http://123.207.32.32:8000/home/multidata").then(res=>{// return res.json()// }).then(data =>{// console.log(data)// })// 3.await/async const res = await fetch("http://123.207.32.32:8000/home/multidata")const data = await res.json();console.log(data);// 修改state數據context.commit("changeBanners",data.data.banner.list)context.commit("changeRecommends",data.data.recommend.list)return 'aaaa';// return new Promise(async (resolve,reject)=>{// const res = await fetch("http://123.207.32.32:8000/home/multidata")// const data = await res.json();// console.log(data);// // 修改state數據// context.commit("changeBanners",data.data.banner.list)// context.commit("changeRecommends",data.data.recommend.list)// // reject()// resolve("aaaa")// })}}
}
- HomeCom.vue
<template><div><h2>Home Page</h2><ul><template v-for="item in $store.state.home.banners" :key="item.acm"><li>{{ item.title }}</li></template></ul></div>
</template> <script setup>import { useStore } from 'vuex';// 進行vuex網絡請求const store = useStore()store.dispatch("fetchHomeMultidataAction").then(res=>{console.log("home中的then被回調:",res)})
</script><style scoped></style>
Modules-默認模塊化
Home.vue
<template><div><h2>Home Page</h2><h2>Counter模塊的counter:{{ $store.state.counter.count }}</h2><h2>Counter模塊的doubleCounter:{{ $store.getters.doubleCount }}</h2><button @click="incrementCount">count模塊+1</button></div>
</template> <script setup>import { useStore } from 'vuex';// 進行vuex網絡請求const store = useStore()function incrementCount(){store.dispatch("incrementCountAction")}
</script><style scoped></style>
store/index.js文件
const counter = {namespaced:true,state:() =>({count:99}),mutations:{incrementCount(state){state.count++}},getters:{doubleCount(state,getters,rootState){return state.count + rootState.rootCounter}},actions:{incrementCountAction(context){context.commit("incrementCount")}}
}export default counter
修改模塊子的值
HomeCom.vue
<template><div><h2>Home Page</h2><h2>Counter模塊的counter:{{ $store.state.counter.count }}</h2><h2>Counter模塊的doubleCounter:{{ $store.getters["counter/doubleCount"] }}</h2><button @click="incrementCount">count模塊+1</button></div>
</template> <script setup>import { useStore } from 'vuex';// 進行vuex網絡請求const store = useStore()function incrementCount(){store.dispatch("counter/incrementCountAction")}// module修改或派發根組件// 如果我們希望在action中修改root中的state,那么有如下方式// changeNameAction({commit,dispatch,state,rootState,getters,rootGetters}){// commit("changeName","kobe");// commit("changeNameRootName",null,{root:true});// dispatch("changeRootNameAction",null,{root:true});// }
</script><style scoped></style>
感謝觀看,我們下次見