更新於 2024/10/25閱讀時間約 11 分鐘

EP48 - 狀態管理

State Management 管理的部分都蠻值得一看的
但前提是什麼狀態啊?哈
一知半解的技術郎 www

什麼是狀態管理?What is State Management?

技術上,每個 Vue 組件實例已經「管理」自己的響應式狀態。以一個簡單的計數器組件為例:

<script setup>
import { ref } from 'vue'

// 狀態
const count = ref(0)

// 行為
function increment() {
count.value++
}
</script>

<!-- 視圖 -->
<template>{{ count }}</template>

這是一個獨立的單元,包含以下部分:

  • 狀態:驅動我們應用程式的真實來源;
  • 視圖:狀態的聲明式映射;
  • 行為:狀態可能在響應於來自視圖的用戶輸入時發生變化的方式。

這是「單向數據流」概念的簡單表示:

然而,當我們有多個組件共享相同的狀態時,這種簡單性開始崩潰:

  1. 多個視圖可能依賴於同一段狀態。
  2. 來自不同視圖的行為可能需要修改相同的狀態。

對於第一種情況,一個可能的解決方法是將共享狀態「提升」到共同的祖先組件,然後作為屬性傳遞下去。然而,在具有深層次層次結構的組件樹中,這很快就變得繁瑣,導致另一個問題,即「屬性傳遞」。

對於第二種情況,我們經常發現自己不得不求助於解決方案,例如通過模板引用訪問直接的父子實例,或者試圖通過發出的事件來修改和同步多個狀態副本。這兩種模式都很脆弱,並迅速導致不可維護的代碼。

一個更簡單且直接的解決方案是將共享狀態提取出組件,並在全局單例中進行管理。這樣,我們的組件樹變成一個大型「視圖」,無論它們在樹中的哪個位置,任何組件都可以訪問狀態或觸發行為!

簡單的狀態管理與反應性 API - Simple State Management with Reactivity API​

如果您有一個狀態需要由多個實例共享,可以使用 reactive() 創建一個反應性對象,然後將其導入多個組件:

// store.js
import { reactive } from 'vue'

export const store = reactive({
count: 0
})
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From A: {{ store.count }}</template>
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From B: {{ store.count }}</template>

現在,每當 store 對象被修改時,<ComponentA><ComponentB> 都會自動更新它們的視圖,我們現在擁有一個單一的真相來源。

然而,這也意味著任何導入 store 的組件都可以隨意修改它:

<template>
<button @click="store.count++">
From B: {{ store.count }}
</button>
</template>

雖然這在簡單情況下是有效的,但全局狀態可以被任何組件任意修改,從長遠來看並不是很可維護。為了確保狀態修改邏輯集中管理,就像狀態本身一樣,建議在 store 上定義方法,並使用表達操作意圖的名稱:

// store.js
import { reactive } from 'vue'

export const store = reactive({
count: 0,
increment() {
this.count++
}
})
<template>
<button @click="store.increment()">
From B: {{ store.count }}
</button>
</template>

Try it in the playground

提示:請注意,點擊處理器使用 store.increment() 並帶有括號——這是必要的,以便在正確的 this 上下文中調用該方法,因為它不是組件方法。

雖然這裡我們使用單個反應性對象作為商店,您還可以共享使用其他反應性 API(如 ref()computed())創建的反應性狀態,甚至從可組合函數返回全局狀態:

import { ref } from 'vue'

// global state, created in module scope
const globalCount = ref(1)

export function useCount() {
// local state, created per-component
const localCount = ref(1)

return {
globalCount,
localCount
}
}

Vue 的反應性系統與組件模型解耦的特性使其具有極大的靈活性。

SSR 考量 - SSR Considerations​

如果您正在構建一個利用伺服器端渲染(SSR)的應用程序,上述模式可能會導致問題,因為該商店是跨多個請求共享的單例。這在 SSR 指南中有更詳細的討論。

Pinia

雖然我們自己手動開發的狀態管理解決方案在簡單的情境下已經足夠,但在大型生產應用中還有許多需要考量的事項:

  • 更強的團隊協作慣例
  • 與 Vue DevTools 的整合,包括時間線、組件內檢查和時間旅行調試
  • 熱模組替換(Hot Module Replacement)
  • 支援伺服器端渲染

Pinia 是一個狀態管理庫,實現了上述所有功能。它由 Vue 核心團隊維護,並且可用於 Vue 2 和 Vue 3。

現有用戶可能熟悉 Vuex,這是之前 Vue 的官方狀態管理庫。隨著 Pinia 在生態系統中扮演相同角色,Vuex 現在處於維護模式。它仍然可用,但將不再接收新功能。建議新應用使用 Pinia。

Pinia 最初是探索 Vuex 下一個版本可能的樣子,並融合了核心團隊對 Vuex 5 討論中的許多想法。最終,我們意識到 Pinia 已經實現了我們在 Vuex 5 中想要的大多數功能,因此決定將其作為新的推薦方案。

與 Vuex 相比,Pinia 提供了更簡單的 API,儀式性更少,提供組合 API 樣式的 API,最重要的是,當與 TypeScript 一起使用時,具有穩固的類型推斷支援。

看來還是有得學! 好多庫要學~
雖然不用自己從頭刻,但學習使用也是很累人的www
Pinia來看個範例吧~

Pinia有範例嗎?

以下是一個使用 Pinia 進行狀態管理的簡單範例,這個範例展示了如何創建一個 Pinia 存儲並在組件中使用它。

1. 安裝 Pinia

首先,你需要安裝 Pinia。在你的 Vue 項目中運行以下命令:

npm install pinia

2. 創建 Pinia 存儲

在你的 src 目錄下創建一個 stores 文件夾,並在裡面創建一個 counterStore.js 文件。

// src/stores/counterStore.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})

3. 在應用中使用 Pinia

接下來,在你的 main.js 中引入並安裝 Pinia。

// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)

const pinia = createPinia()
app.use(pinia)

app.mount('#app')

4. 使用存儲的組件範例

然後,你可以在任何組件中使用這個存儲。例如,創建一個 Counter.vue 組件:

<!-- src/components/Counter.vue -->
<template>
<div>
<h1>Count: {{ counter.count }}</h1>
<button @click="counter.increment">Increment</button>
<button @click="counter.decrement">Decrement</button>
</div>
</template>

<script setup>
import { useCounterStore } from '../stores/counterStore'

const counter = useCounterStore()
</script>

5. 在 App.vue 中引入 Counter

最後,將 Counter 組件引入到你的 App.vue 中並顯示它:

<!-- src/App.vue -->
<template>
<div>
<h1>My Counter App</h1>
<Counter />
</div>
</template>

<script setup>
import Counter from './components/Counter.vue'
</script>

完成

現在你已經設置了一個簡單的 Vue 應用,使用 Pinia 作為狀態管理。這個應用包含一個計數器,你可以增加和減少計數。這樣的結構能夠讓你輕鬆地管理全局狀態。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.