本文首發於微信公眾號:大遷世界, 我的微信:qq449245884,我會第一時間和你分享前端行業趨勢,學習途徑等等。
更多開源作品請看 GitHub https://github.com/qq449245884/xiaozhi ,包含一線大廠面試完整考點、資料以及我的系列文章。
隨著Vue 3越來越受重視併成為預設版本,許多事情正在發生變化,生態系統逐漸完善中。直到最近,Vue3 的狀態管理預設推薦的是使用 Pinia。這節課,我們根據專案的規模,探索不同的狀態管理方式,並嘗試預測 Vue 中狀態管理的未來會是什麼樣子。
響應式 API
在options API中,我們可以使用 data()
選項為一個元件宣告響應式資料。在內部,返回的物件被包在響應式幫助器中。這個幫助器也可以作為一個公共API使用。
如果是多個資料被多個例項共享的狀態,那麼 可以使用 reactive()
來建立一個 reactive 物件,然後從多個元件中匯入它。
import { reactive } from "vue";
export const store = {
state: reactive({
heroes: ['Aragorn', 'Legolas', 'Gimli', 'Gandalf']
}),
addHero(hero) {
this.state.heroes.push(hero);
}
};
透過這種方法,資料被集中起來,並可以在各個元件之間重複使用。這可能是一個簡單的選擇,對一個小的應用程式來說佔用的空間最小。
組合
一個類似的概念,即composition API帶來的概念,是使用一個組合 。這種模式在React 那麼非常流行,結合Vue強大的響應性機制,可以編寫一些優雅的、可重複使用的可組合,比如下面這些:
import { ref, computed } from "vue";
import fakeApiCall from "../api";
export default function useFellowship() {
const heroes = ref([]);
const loading = ref(false);
async function init() {
loading.value = true;
heroes.value = await fakeApiCall();
loading.value = false;
}
return {
heroes: computed(() => heroes.value),
loading: computed(() => loading.value),
init
};
}
然後,可以這樣使用:
<template>
<p v-if="loading">Loading...</p>
<p v-else>Companions: {{ heroes.join(", ") }}</p>
</template>
<script>
import useFellowship from "../composables/useFellowship";
import { computed } from "vue";
export default {
name: "MiddleEarth",
setup() {
const { heroes, loading, init } = useFellowship();
init();
return {
heroes: computed(() => heroes.value),
loading,
};
},
};
</script>
事例地址:https://codesandbox.io/s/composables-middle-earth-07yc6h?file...
這種模式最初是為了取代 mixins 而引入的,因為現在的組合比繼承更受歡迎。但它也可以用來在元件之間共享狀態。這也是許多為取代 Vuex 而出現的庫背後的主要想法。
Vuex 4
Vuex是不會消失的。它支援Vue 3,具有相同的API和最小的破壞性變化(這可能是其他庫應該注意的)。唯一的變化是,安裝必須發生在一個 Vue 例項上,而不是直接安裝在 Vue 原型上。
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
Vuex 4 仍在維護中。不過,不會再有很多新的功能被新增到它裡面。如果你已經有一個使用Vuex 3的專案,並想推遲遷移到其他東西上,這是一個不錯的選擇。
Pinia
Pinia 開始是一個實驗,但很快就成為 Vue 3 的主要選擇。它提供了比 Vuex 更多的 API ,有更好的架構和更直觀的語法,充分利用了組合API。
在開發工具的支援上(狀態檢查、帶動作的時間線和時間旅行的能力),以及 Vuex 所提供的使用外掛的擴充套件性,pinia 在設計上是型別安全和模組化的,這是使用Vuex時最大的兩個痛點。
此外,定義 story 的語法與 Vuex 模組非常相似,這使得遷移的工作量非常小,而在使用該 store 時,用到的 API,接近於 Vue3 使用組合API的方式。
import { defineStore } from 'pinia'
export const useFellowship = defineStore('fellowship', {
state: () => {
return { heroes: ['Aragorn', 'Legolas', 'Gimli', 'Gandalf'] }
},
actions: {
addHero(hero) {
this.heroes.push(hero)
},
},
})
<script>
import { useFellowship } from '@/stores/fellowship'
export default {
setup() {
const fellowship = useFellowship()
// 對狀態的訪問
//可以直接進行
console.log(fellowship.heroes)
// Using an action
fellowship.addHero('Boromir')
},
}
</script>
你可能已經注意到的,最大的區別是 mutations 完全消失了。它們通常被認為是極其冗長的,而使用它們沒有任何真正的好處。此外,也不再需要名稱空間了。有了新的匯入 store 的方式,所有的東西都被設計成了名稱空間。這意味著,在 Pinia 中,你沒有一個帶有多個模組的 store ,而是有多個按需匯入和使用的 store 。
Pinia Setup Store
Pinia支援另一種語法來定義 store。它使用一個定義響應式屬性和方法的函式,並返回它們,與Vue Composition API的 setup 函式非常相似。
import { defineStore } from 'pinia'
export const useFellowship = defineStore('fellowship', () => {
const heroes = ref([]);
function addHero(hero) {
heroes.value.push(hero)
}
return {
heroes,
addHero
};
})
在 Setup Stores 中:
ref()
成為state
屬性computed()
成為getters
function()
成為actions
Setup stores 比 Options Store 帶來了更多的靈活性,因為可以在一個 store 內建立 watchers ,並自由使用任何可組合的。
一個更實際的例子
建立一個 fellowship store,它可以容納一個 heroes
列表,並能新增和刪除對應的元素:
import { defineStore } from 'pinia'
export const useFellowship = defineStore('fellowship', {
state: () => ({
heroes: [],
filter: 'all',
// type will be automatically inferred to number
id: 1
}),
getters: {
aliveHeroes(state) {
return state.heroes.filter((hero) => hero.isAlive)
},
deadHeroes(state) {
return state.heroes.filter((hero) => !hero.isAlive)
},
filteredHeroes() {
switch (this.filter) {
case 'alive':
return this.aliveHeroes
case 'dead':
return this.deadHeroes
default:
return this.heroes
}
}
},
actions: {
addHero(name) {
if (!name) return
// Directly mutating the state!
this.heroes.push({ name, id: this.id++, isAlive: true })
},
killHero(name) {
this.heroes = this.heroes.map((hero) => {
if (hero.name === name) {
hero.isAlive = false
}
return hero
})
},
setActiveFilter(filter) {
this.filter = filter
}
}
})
如果你熟悉Vuex,那麼理解這段程式碼應該不是什麼難事。
首先,每個 state 都需要一個作為名稱空間的鍵。這裡,我們使用 fellowship
。
state
是一個函式,儲存這個 store 的所有響應性資料,getters
是訪問 store 裡面的資料。state
和 getters
都與Vuex相同。
但對於 actions 來說與 Vuex 差異比較大。上下文引數已經消失了,actions 可以直接透過 this
訪問 state 和 getters 。你可能已經注意到的,actions 直接操作 state,這在Vuex 中是被嚴格禁止的。
最後,由於狀態操作現在是在 actions 進行的,所以 mutations 被完全刪除。
使用 pinia store 很簡單:
<script>
import { useFellowship } from '../store/fellowship'
import HeroFilters from './HeroFilters'
export default {
name: 'MiddleEarth',
components: {
HeroFilters
},
setup() {
const fellowship = useFellowship()
return {
fellowship
}
}
}
</script>
<template>
<div>
<template v-if="fellowship.heroes.length">
<HeroFilters />
<ol>
<li v-for="hero in fellowship.filteredHeroes" :key="hero.id">
{{ hero.name }} - {{ hero.isAlive ? 'Alive' : 'Dead' }}
<button v-if="hero.isAlive" @click="fellowship.killHero(hero.name)">Kill</button>
</li>
</ol>
</template>
<p v-else>Your fellowship is empty</p>
<div>
<input type="text" ref="heroName" />
<input type="button" value="Add new hero" @click="fellowship.addHero($refs.heroName.value)" />
<p>
Sugestions:
<button
v-for="suggestion in ['Aragorn', 'Legolas', 'Gimli']"
:key="suggestion"
@click="fellowship.addHero(suggestion)"
>
{{ suggestion }}
</button>
</p>
</div>
</div>
</template>
所有的邏輯都發生在 setup
函式中。匯入的 useFellowship
鉤子被執行並返回。這樣在 template 就可以直接。
當然,這個元件應該被分解成更小的可重複使用的元件,但為了演示的目的,就先這樣吧。
如果一個不同的元件需要訪問相同的 state,可以用類似的方式來完成。
<script>
import { useFellowship } from '../store/fellowship'
export default {
name: 'HeroFilters',
setup() {
const fellowship = useFellowship()
return {
fellowship
}
}
}
</script>
<template>
<div>
Filter:
<div v-for="filter in ['all', 'dead', 'alive']" :key="filter">
<input
type="radio"
:value="filter"
:id="filter"
@click="fellowship.setActiveFilter(filter)"
v-model="fellowship.filter"
/>
<label :for="filter">{{ filter }}</label>
</div>
</div>
</template>
事例地址:https://codesandbox.io/s/pinia-playground-brgy58?file=/src/co...
從 Vuex 遷移到 Pinia
Pinia的文件很樂觀,認為程式碼可以在庫之間重複使用,但事實是,架構非常不同,肯定需要重構。首先,在Vuex中,我們有一個帶有多個模組的 store ,而 Pinia 是圍繞著多個 store 的概念建立的。將這一概念過渡到Pinia 中的最簡單方法是,以前使用的每個模組現在都是一個 store 。
此外,mutations
不再存在。相反,這些應該轉換為直接訪問和改變狀態的操作。
Actions 不再接受上下文作為其第一個引數。它們應該被更新以直接訪問狀態或任何其他上下文屬性。這同樣適用於 rootState
、rootGetters
等,因為單一全域性儲存的概念並不存在。如果你想使用另一個 store,需要明確地匯入它。
很明顯,對於大型專案來說,遷移將是複雜和耗時的,但希望大量的模板程式碼將被消除,store 將遵循一個更乾淨和模組化的架構。改造可以逐個模組進行,而不是一次性改造所有內容。實際上,在遷移過程中,可以將 Pinea 和Vuex混合在一起,這種方法對於複雜的專案來說也是不錯的選擇。
總結
預測未來並不容易,但就目前而言,Pinea 是最安全的賭注。它提供了一個模組化的架構,透過設計實現型別安全,並消除了模板程式碼。如果你要用Vue 3開始一個新的專案,Pinia 是值得推薦的選擇。
如果你已經在使用Vuex,你可以在遷移到Pinia之前升級到第4版,因為這個過程看起來很簡單,但需要大量的時間。
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
作者:Fotis Adamakis 譯者:前端小智 來源:mediun
原文:
https://fadamakis.medium.com/the-future-of-state-management-i...
交流
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。