EP48 - 狀態管理

閱讀時間約 11 分鐘
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>

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

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

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

raw-image

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

  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 作為狀態管理。這個應用包含一個計數器,你可以增加和減少計數。這樣的結構能夠讓你輕鬆地管理全局狀態。

2會員
63內容數
分享生活趣事~
留言0
查看全部
發表第一個留言支持創作者!
卡關的人生 的其他內容
前後端路由的協作是現代應用開發的重要部分。後端路由根據用戶的 URL 發送相應的回應,而前端路由則使得單頁應用(SPA)能在不重新加載整個頁面的情況下更新內容。前端路由會攔截用戶的導航,並動態加載所需的組件。
Vite 作為輕量快速的建置工具,原生支持 Vue SFC,並且提供簡化的配置與優越的開發體驗。文章還介紹了 IDE 支援、測試工具(如 Cypress 和 Vitest)、Linting 和格式化工具的使用,幫助開發者提高開發效率與代碼質量。
Vue 單文件元件(SFC)是 *.vue 文件,將模板、邏輯和樣式封裝在一個文件中,提供模組化開發。SFC 需建置步驟,但帶來多項好處,如元件範圍內的 CSS、預編譯模板和熱模塊替換(HMR)。適合單頁應用、靜態網站生成等前端專案。
<Suspense> 是 Vue 3 的一個實驗性組件,用於協調異步組件的加載狀態,簡化異步處理。它能在等待多個嵌套組件的異步依賴解決時顯示加載指示器,避免每個組件獨立處理加載和錯誤狀態,從而提高用戶體驗。
<Teleport> 是 Vue.js 的內建組件,允許我們將組件的部分內容「傳送」到 DOM 的不同位置,即使這些內容邏輯上屬於某個組件,但可以在視覺上顯示在其他位置,常用於模態框和覆蓋層等情境。
學會了使用 <KeepAlive>,這是一個內建組件,允許我們在動態切換多個組件時有條件地緩存組件實例,避免狀態丟失並提高速度。使用 include 和 exclude 屬性來自定義緩存行為,並透過 max 屬性限制緩存的實例數量。
前後端路由的協作是現代應用開發的重要部分。後端路由根據用戶的 URL 發送相應的回應,而前端路由則使得單頁應用(SPA)能在不重新加載整個頁面的情況下更新內容。前端路由會攔截用戶的導航,並動態加載所需的組件。
Vite 作為輕量快速的建置工具,原生支持 Vue SFC,並且提供簡化的配置與優越的開發體驗。文章還介紹了 IDE 支援、測試工具(如 Cypress 和 Vitest)、Linting 和格式化工具的使用,幫助開發者提高開發效率與代碼質量。
Vue 單文件元件(SFC)是 *.vue 文件,將模板、邏輯和樣式封裝在一個文件中,提供模組化開發。SFC 需建置步驟,但帶來多項好處,如元件範圍內的 CSS、預編譯模板和熱模塊替換(HMR)。適合單頁應用、靜態網站生成等前端專案。
<Suspense> 是 Vue 3 的一個實驗性組件,用於協調異步組件的加載狀態,簡化異步處理。它能在等待多個嵌套組件的異步依賴解決時顯示加載指示器,避免每個組件獨立處理加載和錯誤狀態,從而提高用戶體驗。
<Teleport> 是 Vue.js 的內建組件,允許我們將組件的部分內容「傳送」到 DOM 的不同位置,即使這些內容邏輯上屬於某個組件,但可以在視覺上顯示在其他位置,常用於模態框和覆蓋層等情境。
學會了使用 <KeepAlive>,這是一個內建組件,允許我們在動態切換多個組件時有條件地緩存組件實例,避免狀態丟失並提高速度。使用 include 和 exclude 屬性來自定義緩存行為,並透過 max 屬性限制緩存的實例數量。
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
在過去兩年中,我持續運用 Notion 進行個人管理,個人管理的模板也逐漸定型,藉此分享個人管理模板的使用心得。
Thumbnail
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
在過去兩年中,我持續運用 Notion 進行個人管理,個人管理的模板也逐漸定型,藉此分享個人管理模板的使用心得。
Thumbnail
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找