什麽是狀态管理?
理論上(shàng)來說,每一(yī)個 Vue 組件實例都(dōu)已經在“管理”它自(zì)己的響應式狀态了(le)。我們以一(yī)個簡單的計數(shù)器組件為(wèi)例:
vue
<script>
export default {
// 狀态
data() {
return {
count: 0
}
},
// 動作(zuò)
methods: {
increment() {
this.count++
}
}
}
</script>
<!-- 視(shì)圖 -->
<template>{{ count }}</template>
它是一(yī)個獨立的單元,由以下(xià)幾個部分組成:
狀态:驅動整個應用的數(shù)據源;
視(shì)圖:對狀态的一(yī)種聲明(míng)式映射;
交互:狀态根據用戶在視(shì)圖中的輸入而作(zuò)出相應變更的可(kě)能(néng)方式。
然而,當我們有多個組件共享一(yī)個共同的狀态時(shí),就沒有這(zhè)麽簡單了(le):
多個視(shì)圖可(kě)能(néng)都(dōu)依賴于同一(yī)份狀态。
來自(zì)不同視(shì)圖的交互也可(kě)能(néng)需要(yào)更改同一(yī)份狀态。
對于情景 1,一(yī)個可(kě)行(xíng)的辦法是将共享狀态“提升”到共同的祖先組件上(shàng)去,再通(tōng)過 props 傳遞下(xià)來。然而在深層次的組件樹(shù)結構中這(zhè)麽做(zuò)的話,很(hěn)快(kuài)就會使得代碼變得繁瑣冗長(cháng)。這(zhè)會導緻另一(yī)個問(wèn)題:Prop 逐級透傳問(wèn)題。
對于情景 2,我們經常發現自(zì)己會直接通(tōng)過模闆引用獲取父/子實例,或者通(tōng)過觸發的事件嘗試改變和(hé)同步多個狀态的副本。但(dàn)這(zhè)些模式的健壯性都(dōu)不甚理想,很(hěn)容易就會導緻代碼難以維護。
一(yī)個更簡單直接的解決方案是抽取出組件間(jiān)的共享狀态,放在一(yī)個全局單例中來管理。這(zhè)樣我們的組件樹(shù)就變成了(le)一(yī)個大的“視(shì)圖”,而任何位置上(shàng)的組件都(dōu)可(kě)以訪問(wèn)其中的狀态或觸發動作(zuò)。
用響應式 API 做(zuò)簡單狀态管理
在選項式 API 中,響應式數(shù)據是用 data() 選項聲明(míng)的。在內(nèi)部,data() 的返回值對象會通(tōng)過 reactive() 這(zhè)個公開(kāi)的 API 函數(shù)轉為(wèi)響應式。
如(rú)果你有一(yī)部分狀态需要(yào)在多個組件實例間(jiān)共享,你可(kě)以使用 reactive() 來創建一(yī)個響應式對象,并将它導入到多個組件中:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
vue
<!-- ComponentA.vue -->
<script>
import { store } from './store.js'
export default {
data() {
return {
store
}
}
}
</script>
<template>From A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script>
import { store } from './store.js'
export default {
data() {
return {
store
}
}
}
</script>
<template>From B: {{ store.count }}</template>
現在每當 store 對象被更改時(shí),<ComponentA> 與 <ComponentB> 都(dōu)會自(zì)動更新它們的視(shì)圖。現在我們有了(le)單一(yī)的數(shù)據源。
然而,這(zhè)也意味着任意一(yī)個導入了(le) store 的組件都(dōu)可(kě)以随意修改它的狀态:
template
<template>
<button @click="store.count++">
From B: {{ store.count }}
</button>
</template>
雖然這(zhè)在簡單的情況下(xià)是可(kě)行(xíng)的,但(dàn)從(cóng)長(cháng)遠來看(kàn),可(kě)以被任何組件任意改變的全局狀态是不太容易維護的。為(wèi)了(le)确保改變狀态的邏輯像狀态本身一(yī)樣集中,建議(yì)在 store 上(shàng)定義方法,方法的名稱應該要(yào)能(néng)表達出行(xíng)動的意圖:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
template
<template>
<button @click="store.increment()">
From B: {{ store.count }}
</button>
</template>
TIP
請(qǐng)注意這(zhè)裏點擊的處理函數(shù)使用了(le) store.increment(),帶上(shàng)了(le)圓括号作(zuò)為(wèi)內(nèi)聯表達式調用,因為(wèi)它并不是組件的方法,并且必須要(yào)以正确的 this 上(shàng)下(xià)文來調用。
除了(le)我們這(zhè)裏用到的單個響應式對象作(zuò)為(wèi)一(yī)個 store 之外(wài),你還可(kě)以使用其他(tā)響應式 API 例如(rú) ref() 或是 computed(),或是甚至通(tōng)過一(yī)個組合式函數(shù)來返回一(yī)個全局狀态:
js
import { ref } from 'vue'
// 全局狀态,創建在模塊作(zuò)用域下(xià)
const globalCount = ref(1)
export function useCount() {
// 局部狀态,每個組件都(dōu)會創建
const localCount = ref(1)
return {
globalCount,
localCount
}
}
事實上(shàng),Vue 的響應性系統與組件層是解耦的,這(zhè)使得它非常靈活。
SSR 相關細節
如(rú)果你正在構建一(yī)個需要(yào)利用服務端渲染 (SSR) 的應用,由于 store 是跨多個請(qǐng)求共享的單例,上(shàng)述模式可(kě)能(néng)會導緻問(wèn)題。這(zhè)在 SSR 指引那(nà)一(yī)章(zhāng)節會讨論更多細節。
Pinia
雖然我們的手動狀态管理解決方案在簡單的場景中已經足夠了(le),但(dàn)是在大規模的生産應用中還有很(hěn)多其他(tā)事項需要(yào)考慮:
更強的團隊協作(zuò)約定
與 Vue DevTools 集成,包括時(shí)間(jiān)軸、組件內(nèi)部審查和(hé)時(shí)間(jiān)旅行(xíng)調試
模塊熱(rè)更新 (HMR)
服務端渲染支持
Pinia 就是一(yī)個實現了(le)上(shàng)述需求的狀态管理庫,由 Vue 核心團隊維護,對 Vue 2 和(hé) Vue 3 都(dōu)可(kě)用。
現有用戶可(kě)能(néng)對 Vuex 更熟悉,它是 Vue 之前的官方狀态管理庫。由于 Pinia 在生态系統中能(néng)夠承擔相同的職責且能(néng)做(zuò)得更好,因此 Vuex 現在處于維護模式。它仍然可(kě)以工(gōng)作(zuò),但(dàn)不再接受新的功能(néng)。對于新的應用,建議(yì)使用 Pinia。
事實上(shàng),Pinia 最初正是為(wèi)了(le)探索 Vuex 的下(xià)一(yī)個版本而開(kāi)發的,因此整合了(le)核心團隊關于 Vuex 5 的許多想法。最終,我們意識到 Pinia 已經實現了(le)我們想要(yào)在 Vuex 5 中提供的大部分內(nèi)容,因此決定将其作(zuò)為(wèi)新的官方推薦。
相比于 Vuex,Pinia 提供了(le)更簡潔直接的 API,并提供了(le)組合式風(fēng)格的 API,最重要(yào)的是,在使用 TypeScript 時(shí)它提供了(le)更完善的類型推導。
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發