做(zuò)自(zì)由與創造的先行(xíng)者

狀态管理

Vue.js中文手冊

什麽是狀态管理? ​

理論上(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)發
下(xià)一(yī)篇:測試
上(shàng)一(yī)篇:路由