1_應(yīng)用狀態(tài)管理
1.1_狀態(tài)管理
在開(kāi)發(fā)中,應(yīng)用程序需要處理各種各樣的數(shù)據(jù),這些數(shù)據(jù)需要保存在應(yīng)用程序中的某一個(gè)位置,對(duì)于這些數(shù)據(jù)的管理就稱之為是 狀態(tài)管理。
在前面是如何管理自己的狀態(tài)呢?
- 在Vue開(kāi)發(fā)中,使用組件化的開(kāi)發(fā)方式;
- 而在組件中定義data或者在setup中返回使用的數(shù)據(jù),這些數(shù)據(jù)稱之為state;
- 在模塊template中可以使用這些數(shù)據(jù),模塊最終會(huì)被渲染成DOM,稱之為View;
- 在模塊中會(huì)產(chǎn)生一些行為事件,處理這些行為事件時(shí),有可能會(huì)修改state,這些行為事件稱之為actions;
1.2_復(fù)雜的狀態(tài)管理
JavaScript開(kāi)發(fā)的應(yīng)用程序,已經(jīng)變得越來(lái)越復(fù)雜了:
- JavaScript需要管理的狀態(tài)越來(lái)越多,越來(lái)越復(fù)雜;
- 這些狀態(tài)包括服務(wù)器返回的數(shù)據(jù)、緩存數(shù)據(jù)、用戶操作產(chǎn)生的數(shù)據(jù)等等;
- 也包括一些UI的狀態(tài),比如某些元素是否被選中,是否顯示加載動(dòng)效,當(dāng)前分頁(yè);
當(dāng)?shù)膽?yīng)用遇到多個(gè)組件共享狀態(tài)時(shí),單向數(shù)據(jù)流的簡(jiǎn)潔性很容易被破壞:
- 多個(gè)視圖依賴于同一狀態(tài);
- 來(lái)自不同視圖的行為需要變更同一狀態(tài);
是否可以通過(guò)組件數(shù)據(jù)的傳遞來(lái)完成呢?
- 對(duì)于一些簡(jiǎn)單的狀態(tài),確實(shí)可以通過(guò)props的傳遞或者Provide的方式來(lái)共享狀態(tài);
- 但是對(duì)于復(fù)雜的狀態(tài)管理來(lái)說(shuō),顯然單純通過(guò)傳遞和共享的方式是不足以解決問(wèn)題的,比如兄弟組件如何共享數(shù)據(jù)呢
1.3_Vuex的狀態(tài)管理
管理不斷變化的state本身是非常困難的:
- 狀態(tài)之間相互會(huì)存在依賴,一個(gè)狀態(tài)的變化會(huì)引起另一個(gè)狀態(tài)的變化,View頁(yè)面也有可能會(huì)引起狀態(tài)的變化;
- 當(dāng)應(yīng)用程序復(fù)雜時(shí),state在什么時(shí)候,因?yàn)槭裁丛蚨l(fā)生了變化,發(fā)生了怎么樣的變化,會(huì)變得非常難以控制和追蹤;
因此,是否可以考慮將組件的內(nèi)部狀態(tài)抽離出來(lái),以一個(gè)全局單例的方式來(lái)管理呢?
- 在這種模式下,組件樹(shù)構(gòu)成了一個(gè)巨大的 “試圖View”;
- 不管在樹(shù)的哪個(gè)位置,任何組件都能獲取狀態(tài)或者觸發(fā)行為;
- 通過(guò)定義和隔離狀態(tài)管理中的各個(gè)概念,并通過(guò)強(qiáng)制性的規(guī)則來(lái)維護(hù)視圖和狀態(tài)間的獨(dú)立性,的代碼邊會(huì)變得更加結(jié)構(gòu)化和易于維護(hù)、跟蹤;
這就是Vuex背后的基本思想,它借鑒了Flux、Redux、Elm(純函數(shù)語(yǔ)言,redux有借鑒它的思想);
Vue官方也在推薦使用Pinia進(jìn)行狀態(tài)管理,后續(xù)學(xué)習(xí)
參考官網(wǎng)的圖
2_Vuex的基本使用
2.1_安裝
npm install
2.2_創(chuàng)建Store
每一個(gè)Vuex應(yīng)用的核心就是store(倉(cāng)庫(kù)): store本質(zhì)上是一個(gè)容器,它包含著應(yīng)用中大部分的狀態(tài)(state);
Vuex和單純的全局對(duì)象有什么區(qū)別?
-
第一:Vuex的狀態(tài)存儲(chǔ)是響應(yīng)式的。 當(dāng)Vue組件從store中讀取狀態(tài)的時(shí)候,若store中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)被更新;
-
第二:不能直接改變store中的狀態(tài)。
- 改變store中的狀態(tài)的唯一途徑就顯示提交 (commit) mutation;
- 這樣使得可以方便的跟蹤每一個(gè)狀態(tài)的變化,從而讓能夠通過(guò)一些工具幫助更好的管理應(yīng)用的狀態(tài);
demo:
(1)在src文件夾下,建立一個(gè)新文件夾store,在該文件夾下,一般創(chuàng)建index.js文件,也可根據(jù)實(shí)際開(kāi)發(fā)創(chuàng)建。
src/store/index.js
import { createStore } from 'vuex'
const store = createStore({
state: () => ({
counter: 100
})
})
(2)在main.js注冊(cè)
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
(3)在App.vue中調(diào)用
<template>
<div class="app">
<!-- store中的counter -->
<h2>App當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
</div>
</template>
2.3_組件中使用store
在組件中使用store,按照如下的方式:
- 在模板tempte中使用,比如2.2的demo
- 在options api中使用,比如computed;
- 在setup中使用;
<template>
<div class="app">
<!-- 在模板tempte中使用store -->
<h2>Home當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
<h2>Computed當(dāng)前計(jì)數(shù): {{ storeCounter }}</h2>
<h2>Setup當(dāng)前計(jì)數(shù): {{ counter }}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default {
//在options api中使用,比如computed;
computed: {
storeCounter() {
return this.$store.state.counter
}
}
}
</script>
<!-- 在setup中使用store -->
<script setup>
import { toRefs } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const { counter } = toRefs(store.state)
function increment() {
store.commit("increment")
}
</script>
3_核心概念State
3.1_單一狀態(tài)樹(shù)
Vuex 使用單一狀態(tài)樹(shù):
- 用一個(gè)對(duì)象就包含了全部的應(yīng)用層級(jí)的狀態(tài);
- 采用的是SSOT,Single Source of Truth,也可以翻譯成單一數(shù)據(jù)源;
這也意味著,每個(gè)應(yīng)用將僅僅包含一個(gè) store 實(shí)例, 單狀態(tài)樹(shù)和模塊化并不沖突,后面會(huì)涉及到module的概念;
單一狀態(tài)樹(shù)的優(yōu)勢(shì):
- 如果狀態(tài)信息是保存到多個(gè)Store對(duì)象中的,那么之后的管理和維護(hù)等等都會(huì)變得特別困難;
- 所以Vuex也使用了單一狀態(tài)樹(shù)來(lái)管理應(yīng)用層級(jí)的全部狀態(tài);
- 單一狀態(tài)樹(shù)能夠讓最直接的方式找到某個(gè)狀態(tài)的片段;
- 而且在之后的維護(hù)和調(diào)試過(guò)程中,也可以非常方便的管理和維護(hù);
3.2_組件獲取狀態(tài)
在前面的demo已知如何在組件中獲取狀態(tài)了。
當(dāng)然,如果覺(jué)得那種方式有點(diǎn)繁瑣(表達(dá)式過(guò)長(zhǎng)),可以使用計(jì)算屬性:
computed: {
storeCounter() {
return this.$store.state.counter
}
}
但是,如果有很多個(gè)狀態(tài)都需要獲取話,可以使用mapState
的輔助函數(shù):
- mapState的方式一:對(duì)象類(lèi)型;
- mapState的方式二:數(shù)組類(lèi)型;
- 也可以使用展開(kāi)運(yùn)算符和來(lái)原有的computed混合在一起;
3.3_在setup中使用mapState
在setup中如果單個(gè)獲取狀態(tài)是非常簡(jiǎn)單的, 通過(guò)useStore拿到store后去獲取某個(gè)狀態(tài)即可。
但是如果使用 mapState 如何獲取狀態(tài)?
(1) 默認(rèn)情況下,Vuex并沒(méi)有提供非常方便的使用mapState的方式,下面這種方式不建議使用
<template>
<div class="app">
<!-- 在模板中直接使用多個(gè)狀態(tài) -->
<h2>name: {{ $store.state.name }}</h2>
<h2>level: {{ $store.state.level }}</h2>
<h2>avatar: {{ $store.state.avatarURL }}</h2>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { mapState, useStore } from 'vuex'
// 一步步完成,步驟繁瑣
const { name, level } = mapState(["name", "level"])
const store = useStore()
const cName = computed(name.bind({ $store: store }))
const cLevel = computed(level.bind({ $store: store }))
</script>
(2)這里進(jìn)行了一個(gè)函數(shù)的封裝,簡(jiǎn)化步驟
src/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
}
使用該函數(shù)
<template>
<div class="app">
<!-- 在模板中直接使用多個(gè)狀態(tài) -->
<h2>name: {{ $store.state.name }}</h2>
<h2>level: {{ $store.state.level }}</h2>
<h2>avatar: {{ $store.state.avatarURL }}</h2>
</div>
</template>
<script setup>
import useState from "../hooks/useState"
// 使用useState封裝函數(shù)
const { name, level } = useState(["name", "level"])
</script>
(3)更推薦下面的使用方式
<template>
<div class="app">
<!-- 在模板中直接使用多個(gè)狀態(tài) -->
<h2>name: {{ $store.state.name }}</h2>
<h2>level: {{ $store.state.level }}</h2>
<h2>avatar: {{ $store.state.avatarURL }}</h2>
</div>
</template>
<script setup>
import { computed, toRefs } from 'vue'
import { mapState, useStore } from 'vuex'
// 3.直接對(duì)store.state進(jìn)行解構(gòu)(推薦)
const store = useStore()
const { name, level } = toRefs(store.state)
</script>
4_核心概念Getters
4.1_getters的基本使用
某些屬性可能需要經(jīng)過(guò)變化后來(lái)使用,這個(gè)時(shí)候可以使用getters
4.2_getters第二個(gè)參數(shù)
getters可以接收第二個(gè)參數(shù)
getters: {
// 2.在該getters屬性中, 獲取其他的getters
message(state, getters) {
return `name:${state.name} level:${state.level} friendTotalAge:${getters.totalAge}`
}
}
4.3_getters的返回函數(shù)
getters: {
// 3.getters是可以返回一個(gè)函數(shù)的, 調(diào)用這個(gè)函數(shù)可以傳入?yún)?shù)(了解)
getFriendById(state) {
return function(id) {
const friend = state.friends.find(item => item.id === id)
return friend
}
}
}
4.4_mapGetters的輔助函數(shù)
可以使用mapGetters的輔助函數(shù)
<template>
<div class="app">
<h2>doubleCounter: {{ doubleCounter }}</h2>
<h2>friendsTotalAge: {{ totalAge }}</h2>
<!-- 根據(jù)id獲取某一個(gè)朋友的信息 -->
<h2>id-111的朋友信息: {{ getFriendById(111) }}</h2>
<h2>id-112的朋友信息: {{ getFriendById(112) }}</h2>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(["doubleCounter", "totalAge"]),
...mapGetters(["getFriendById"])
}
}
</script>
也可在setup中使用
<template>
<div class="app">
<h2>message: {{ message }}</h2>
</div>
</template>
<script setup>
import { computed, toRefs } from 'vue';
import { mapGetters, useStore } from 'vuex'
const store = useStore()
// 1.使用mapGetters ,較麻煩
// const { message: messageFn } = mapGetters(["message"])
// const message = computed(messageFn.bind({ $store: store }))
// 2.直接解構(gòu), 并且包裹成ref
// const { message } = toRefs(store.getters)
// 3.針對(duì)某一個(gè)getters屬性使用computed
const message = computed(() => store.getters.message)
</script>
5_核心概念Mutations
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation
5.1_使用
在提交mutation的時(shí)候,會(huì)攜帶一些數(shù)據(jù),這時(shí)可以使用參數(shù),注意payload為對(duì)象類(lèi)型
mutation :{
add(state,payload){
statte.counter + = payload
}
}
提交
$store.commit({
type: "add",
count: 100
})
5.2_Mutation常量類(lèi)型
demo:
(1)在mutaition-type.js,定義常量
export const CHANGE_INFO = "changeInfo"
(2)在store使用常量
mutations: {
[CHANGE_INFO](state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
}
(3)在使用的
5.3_mutation重要原則
一條重要的原則就是,mutation 必須是同步函數(shù)
- 這是因?yàn)閐evtool工具會(huì)記錄mutation的日記;
- 每一條mutation被記錄,devtools都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照;
- 但是在mutation中執(zhí)行異步操作,就無(wú)法追蹤到數(shù)據(jù)的變化;
6_核心概念A(yù)ctions
6.1_基本使用
Action類(lèi)似于mutation,不同在于:
-
Action提交的是mutation,而不是直接變更狀態(tài);
-
Action可以包含任意異步操作;
有一個(gè)非常重要的參數(shù)context:
- context是一個(gè)和store實(shí)例均有相同方法和屬性的context對(duì)象;
- 所以可以從其中獲取到commit方法來(lái)提交一個(gè)mutation,或者通過(guò) context.state 和 context.getters 來(lái)獲取 state 和getters;
具體案例參考這篇文章 :https://blog.csdn.net/qq_21980517/article/details/103398686
6.2_分發(fā)操作
6.3_actions的異步操作
Action 通常是異步的,可以通過(guò)讓action返回Promise,知道 action 什么時(shí)候結(jié)束,然后,在Promise的then中來(lái)處理完成后的操作。
demo:
index.js中,以發(fā)送網(wǎng)絡(luò)請(qǐng)求為例
action{
fetchHomeMultidataAction(context) {
// 1.返回Promise, 給Promise設(shè)置then
// fetch("http://123.207.32.32:8000/home/multidata").then(res => {
// res.json().then(data => {
// console.log(data)
// })
// })
// 2.Promise鏈?zhǔn)秸{(diào)用
// fetch("http://123.207.32.32:8000/home/multidata").then(res => {
// return res.json()
// }).then(data => {
// console.log(data)
// })
return new Promise(async (resolve, reject) => {
// 3.await/async
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
// 修改state數(shù)據(jù)
context.commit("changeBanners", data.data.banner.list)
context.commit("changeRecommends", data.data.recommend.list)
resolve("aaaaa")
})
}
}
test3.vue中
<script setup>
import { useStore } from 'vuex'
// 告訴Vuex發(fā)起網(wǎng)絡(luò)請(qǐng)求
const store = useStore()
store.dispatch("fetchHomeMultidataAction").then(res => {
console.log("home中的then被回調(diào):", res)
})
</script>
7_核心概念Modules
7.1_module的基本使用
Module的理解?
- 由于使用單一狀態(tài)樹(shù),應(yīng)用的所有狀態(tài)會(huì)集中到一個(gè)比較大的對(duì)象,當(dāng)應(yīng)用變得非常復(fù)雜時(shí),store 對(duì)象就有可能變得相當(dāng)臃腫;
- 為了解決以上問(wèn)題,Vuex 允許將 store 分割成模塊(module);
- 每個(gè)模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊
7.2_module的局部狀態(tài)
對(duì)于模塊內(nèi)部的 mutation 和 getter,接收的第一個(gè)參數(shù)是模塊的局部狀態(tài)對(duì)象
7.3_module的命名空間
默認(rèn)情況下,模塊內(nèi)部的action和mutation仍然是注冊(cè)在全局的命名空間中的:
- 這樣使得多個(gè)模塊能夠?qū)ν粋€(gè) action 或 mutation 作出響應(yīng);
- Getter 同樣也默認(rèn)注冊(cè)在全局命名空間;
如果希望模塊具有更高的封裝度和復(fù)用性,可以添加 namespaced: tru
e 的方式使其成為帶命名空間的模塊, 當(dāng)模塊被注冊(cè)后,它的所有 getter、action 及 mutation 都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名
demo:counter,js文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-729765.html
const counter = {
namespaced: true, //命令空間
state: () => ({
count: 99
}),
mutations: {
incrementCount(state) {
console.log(state)
state.count++
}
},
getters: {
doubleCount(state, getters, rootState) {
return state.count + rootState.rootCounter
}
},
actions: {
incrementCountAction(context) {
context.commit("incrementCount")
}
}
}
export default counter
7.4_module修改或派發(fā)根組件
在action中修改root中的state,那么有如下的方式
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-729765.html
到了這里,關(guān)于【Vuex狀態(tài)管理】Vuex的基本使用;核心概念State、Getters、Mutations、Actions、Modules的基本使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!