做自(zì)由與創造的先行者

狀态管理(lǐ)

Vue.js中文(wén)手冊

什(shén)麽是狀态管理(lǐ)? ​

理(lǐ)論上(shàng)來(lái)說,每一個 Vue 組件實例都已經在“管理(lǐ)”它自(zì)己的響應式狀态了(le)。我們以一個簡單的計(jì)數器組件爲例:

vue

<script>

export default {

// 狀态

data() {

return {

count: 0

}

},

// 動作(zuò)

methods: {

increment() {

this.count++

}

}

}

</script>

<!-- 視(shì)圖 -->

<template>{{ count }}</template>

它是一個獨立的單元,由以下(xià)幾個部分組成:

狀态:驅動整個應用(yòng)的數據源;

視(shì)圖:對(duì)狀态的一種聲明(míng)式映射;

交互:狀态根據用(yòng)戶在視(shì)圖中的輸入而作(zuò)出相應變更的可能(néng)方式。

然而,當我們有多個組件共享一個共同的狀态時(shí),就沒有這(zhè)麽簡單了(le):

多個視(shì)圖可能(néng)都依賴于同一份狀态。

來(lái)自(zì)不同視(shì)圖的交互也(yě)可能(néng)需要更改同一份狀态。

對(duì)于情景 1,一個可行的辦法是将共享狀态“提升”到(dào)共同的祖先組件上(shàng)去,再通過 props 傳遞下(xià)來(lái)。然而在深層次的組件樹結構中這(zhè)麽做的話(huà),很(hěn)快(kuài)就會(huì)使得代碼變得繁瑣冗長。這(zhè)會(huì)導緻另一個問題:Prop 逐級透傳問題。

對(duì)于情景 2,我們經常發現(xiàn)自(zì)己會(huì)直接通過模闆引用(yòng)獲取父/子實例,或者通過觸發的事(shì)件嘗試改變和(hé)同步多個狀态的副本。但(dàn)這(zhè)些(xiē)模式的健壯性都不甚理(lǐ)想,很(hěn)容易就會(huì)導緻代碼難以維護。

一個更簡單直接的解決方案是抽取出組件間的共享狀态,放(fàng)在一個全局單例中來(lái)管理(lǐ)。這(zhè)樣我們的組件樹就變成了(le)一個大(dà)的“視(shì)圖”,而任何位置上(shàng)的組件都可以訪問其中的狀态或觸發動作(zuò)。

用(yòng)響應式 API 做簡單狀态管理(lǐ) ​

在選項式 API 中,響應式數據是用(yòng) data() 選項聲明(míng)的。在内部,data() 的返回值對(duì)象會(huì)通過 reactive() 這(zhè)個公開(kāi)的 API 函數轉爲響應式。

如果你(nǐ)有一部分狀态需要在多個組件實例間共享,你(nǐ)可以使用(yòng) reactive() 來(lái)創建一個響應式對(duì)象,并将它導入到(dào)多個組件中:

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>

現(xiàn)在每當 store 對(duì)象被更改時(shí),<ComponentA> 與 <ComponentB> 都會(huì)自(zì)動更新它們的視(shì)圖。現(xiàn)在我們有了(le)單一的數據源。

然而,這(zhè)也(yě)意味着任意一個導入了(le) store 的組件都可以随意修改它的狀态:

template

<template>

<button @click="store.count++">

From B: {{ store.count }}

</button>

</template>

雖然這(zhè)在簡單的情況下(xià)是可行的,但(dàn)從(cóng)長遠來(lái)看(kàn),可以被任何組件任意改變的全局狀态是不太容易維護的。爲了(le)确保改變狀态的邏輯像狀态本身一樣集中,建議(yì)在 store 上(shàng)定義方法,方法的名稱應該要能(né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è)裏點擊的處理(lǐ)函數使用(yòng)了(le) store.increment(),帶上(shàng)了(le)圓括号作(zuò)爲内聯表達式調用(yòng),因爲它并不是組件的方法,并且必須要以正确的 this 上(shàng)下(xià)文(wén)來(lái)調用(yòng)。

除了(le)我們這(zhè)裏用(yòng)到(dào)的單個響應式對(duì)象作(zuò)爲一個 store 之外(wài),你(nǐ)還可以使用(yòng)其他(tā)響應式 API 例如 ref() 或是 computed(),或是甚至通過一個組合式函數來(lái)返回一個全局狀态:

js

import { ref } from 'vue'

// 全局狀态,創建在模塊作(zuò)用(yòng)域下(xià)

const globalCount = ref(1)

export function useCount() {

// 局部狀态,每個組件都會(huì)創建

const localCount = ref(1)

return {

globalCount,

localCount

}

}

事(shì)實上(shàng),Vue 的響應性系統與組件層是解耦的,這(zhè)使得它非常靈活。

SSR 相關細節 ​

如果你(nǐ)正在構建一個需要利用(yòng)服務端渲染 (SSR) 的應用(yòng),由于 store 是跨多個請(qǐng)求共享的單例,上(shàng)述模式可能(néng)會(huì)導緻問題。這(zhè)在 SSR 指引那一章節會(huì)讨論更多細節。

Pinia ​

雖然我們的手動狀态管理(lǐ)解決方案在簡單的場景中已經足夠了(le),但(dàn)是在大(dà)規模的生産應用(yòng)中還有很(hěn)多其他(tā)事(shì)項需要考慮:

更強的團隊協作(zuò)約定

與 Vue DevTools 集成,包括時(shí)間軸、組件内部審查和(hé)時(shí)間旅行調試

模塊熱更新 (HMR)

服務端渲染支持

Pinia 就是一個實現(xiàn)了(le)上(shàng)述需求的狀态管理(lǐ)庫,由 Vue 核心團隊維護,對(duì) Vue 2 和(hé) Vue 3 都可用(yòng)。

現(xiàn)有用(yòng)戶可能(néng)對(duì) Vuex 更熟悉,它是 Vue 之前的官方狀态管理(lǐ)庫。由于 Pinia 在生态系統中能(néng)夠承擔相同的職責且能(néng)做得更好(hǎo),因此 Vuex 現(xiàn)在處于維護模式。它仍然可以工(gōng)作(zuò),但(dàn)不再接受新的功能(néng)。對(duì)于新的應用(yòng),建議(yì)使用(yòng) Pinia。

事(shì)實上(shàng),Pinia 最初正是爲了(le)探索 Vuex 的下(xià)一個版本而開(kāi)發的,因此整合了(le)核心團隊關于 Vuex 5 的許多想法。最終,我們意識到(dào) Pinia 已經實現(xiàn)了(le)我們想要在 Vuex 5 中提供的大(dà)部分内容,因此決定将其作(zuò)爲新的官方推薦。

相比于 Vuex,Pinia 提供了(le)更簡潔直接的 API,并提供了(le)組合式風(fēng)格的 API,最重要的是,在使用(yòng) TypeScript 時(shí)它提供了(le)更完善的類型推導。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:測試
上(shàng)一篇:路由