VueX 會發生什麼?
已發表: 2022-03-10Vuex 是 Vue 應用程序狀態管理的解決方案。 下一個版本——Vuex 4——在正式發布之前正在完成最後的步驟。 此版本將帶來與 Vue 3 的完全兼容性,但不會添加新功能。 雖然 Vuex 一直是一個強大的解決方案,並且是許多開發人員在 Vue 中進行狀態管理的首選,但一些開發人員希望看到更多的工作流問題得到解決。 然而,就在 Vuex 4 剛剛面世之際,Kia King Ishii(Vue 核心團隊成員)正在談論他對 Vuex 5 的計劃,我對所看到的內容感到非常興奮,不得不與您分享全部。 請注意,Vuex 5 計劃尚未最終確定,因此在 Vuex 5 發布之前可能會發生一些變化,但如果它最終與您在本文中看到的內容大致相似,那應該是對開發人員體驗的重大改進。
隨著 Vue 3 及其組合 API 的出現,人們一直在尋找手工構建的簡單替代方案。 例如,您可能不需要 Vuex演示了一個相對簡單但靈活且健壯的模式,用於使用組合 API 以及provide/inject
來創建共享狀態存儲。 然而,正如 Gabor 在他的文章中所說,這個(和其他替代方案)應該只用於較小的應用程序,因為它們缺少所有與代碼無關的東西:社區支持、文檔、約定、良好的 Nuxt 集成和開發人員工具。
最後一個問題一直是我最大的問題之一。 Vue devtools 瀏覽器擴展一直是調試和開發 Vue 應用程序的絕佳工具,而“時間旅行”失去 Vuex 檢查器對於調試任何重要的應用程序來說都是一個相當大的損失。
值得慶幸的是,有了 Vuex 5,我們就可以吃到蛋糕了。 它將更像這些組合 API 替代方案,但保留使用官方狀態管理庫的所有好處。 現在讓我們來看看會發生什麼變化。
定義商店
在我們對 Vuex store 做任何事情之前,我們需要定義一個。 在 Vuex 4 中,存儲定義如下所示:
import { createStore } from 'vuex' export const counterStore = createStore({ state: { count: 0 }, getters: { double (state) { return state.count * 2 } }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
每個 store 有四個部分: state
存儲數據, getters
為你提供計算狀態, mutations
用於改變 state, actions
是從 store 外部調用以完成與 store 相關的任何事情的方法。 通常,動作不只是提交一個突變,如本例所示。 相反,它們用於執行異步任務,因為突變必須是同步的,或者它們只是實現更複雜或多步驟的功能。 動作也不能自己改變狀態; 他們必須使用一個mutator。 那麼 Vuex 5 是什麼樣的呢?
import { defineStore } from 'vuex' export const counterStore = defineStore({ name: 'counter', state() { return { count: 0 } }, getters: { double () { return this.count * 2 } }, actions: { increment () { this.count++ } } })
這裡有一些變化需要注意。 首先,我們使用defineStore
而不是createStore
。 這種差異可以忽略不計,但出於語義原因,我們將在稍後討論。 接下來,我們需要為商店提供一個我們以前不需要的name
。 過去,模塊有自己的名字,但它們不是由模塊本身提供的; 它們只是添加它們的父商店分配給它們的屬性名稱。 現在,沒有模塊。 相反,每個模塊將是一個單獨的商店並有一個名稱。 這個名字被 Vuex 註冊中心使用,我們稍後會談到。
之後,我們需要讓state
成為一個返回初始狀態的函數,而不是僅僅將其設置為初始狀態。 這類似於組件上的data
選項。 我們編寫getters
的方式與我們在 Vuex 4 中所做的非常相似,但不是使用state
作為每個 getter 的參數,您可以使用this
來獲取 state。 同樣, actions
不需要擔心傳入的context
對象:他們可以使用this
來訪問所有內容。 最後,沒有mutations
。 相反,突變與actions
相結合。 Kia 指出,突變常常只是簡單的設置器,使它們變得毫無意義地冗長,因此他們刪除了它們。 他沒有提到直接從 store 外部改變狀態是否“可以”,但我們絕對允許並鼓勵我們直接從一個動作改變狀態,並且 Flux 模式不贊成直接改變狀態。
注意:對於那些更喜歡組合 API 而不是用於創建組件的選項 API 的人,您會很高興地了解到還有一種方法可以以類似於使用組合 API 的方式創建商店。
import { ref, computed } from 'vue' import { defineStore } from 'vuex' export const counterStore = defineStore('counter', { const count = ref(0) const double = computed(() => count.value * 2) function increment () { count.value++ } return { count, double, increment } })
如上所示,名稱作為defineStore
的第一個參數傳入。 其餘的看起來就像組件的組合函數。 這將產生與前面使用選項 API 的示例完全相同的結果。
讓商店實例化
在 Vuex 4 中,與 Vuex 3 相比,情況發生了變化,但我只關注 v4 以防止事情失控。 在 v4 中,當您調用createStore
時,您已經實例化了它。 然後,您可以通過app.use
或直接在您的應用中使用它:
import { createApp } from 'vue' import App from './App.vue' // Your root component import store from './store' // The store definition from earlier const app = createApp(App) app.use(store) app.mount('#app') // Now all your components can access it via `this.$store` // Or you can use in composition components with `useStore()` // ----------------------------------------------- // Or use directly... this is generally discouraged import store from './store' store.state.count // -> 0 store.commit('increment') store.dispatch('increment') store.getters.double // -> 4
這是 Vuex 5 比 v4 更複雜的一件事。 現在每個應用程序都可以獲得一個單獨的 Vuex 實例,這確保每個應用程序可以擁有相同商店的不同實例,而無需在它們之間共享數據。 如果您想在應用程序之間共享商店實例,您可以共享一個 Vuex 實例。
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' // Your root component const app = createApp(App) const vuex = createVuex() // create instance of Vuex app.use(vuex) // use the instance app.mount('#app')
現在您的所有組件都可以訪問 Vuex 實例。 然後將它們導入到要使用它們的組件中,並使用 Vuex 實例來實例化和註冊它們,而不是直接給出你的商店定義:
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
調用$vuex.store
,在 Vuex 實例中實例化並註冊 store。 從那時起,任何時候你在那個 store 上使用$vuex.store
,它都會返回已經實例化的 store 而不是再次實例化它。 您可以直接在createVuex()
創建的 Vuex 實例上調用store
方法。
現在可以通過this.counter
在該組件上訪問您的商店。 如果您為組件使用組合 API,則可以使用useStore
代替this.$vuex.store
:
import { defineComponent } from 'vue' import { useStore } from 'vuex' // import useStore import store from './store' export default defineComponent({ setup () { const counter = useStore(store) return { counter } } })
將 store 直接導入組件並在那裡實例化它有利有弊。 它允許您進行代碼拆分並僅在需要的地方延遲加載存儲,但現在它是直接依賴項,而不是由父級註入(更不用說每次要使用它時都需要導入它)。 如果您想使用依賴注入在整個應用程序中提供它,特別是如果您知道它將在應用程序的根目錄中使用代碼拆分無濟於事,那麼您可以使用provide
:
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' import store from './store' const app = createApp(App) const vuex = createVuex() app.use(vuex) app.provide('store', store) // provide the store to all components app.mount('#app')
你可以將它注入任何你將要使用它的組件中:
import { defineComponent } from 'vue' export default defineComponent({ name: 'App', inject: ['store'] }) // Or with Composition API import { defineComponent, inject } from 'vue' export default defineComponent({ setup () { const store = inject('store') return { store } } })
我對這種額外的冗長並不感到興奮,但它更明確、更靈活,這是我的粉絲。 這種類型的代碼通常在項目開始時立即編寫一次,然後就不會再打擾您了,儘管現在您需要提供每個新商店或每次要使用時都導入它,但是導入或註入代碼模塊是我們通常必須使用其他任何東西的方式,所以它只是讓 Vuex 更符合人們已經傾向於工作的方式。
使用商店
除了喜歡使用組合 API 以與組件相同的方式定義 store 的靈活性和新方法之外,還有一件事讓我比其他任何事情都更興奮:如何使用 store。 這是在 Vuex 4 中使用 store 的樣子。
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
State
、 getters
、 mutations
和actions
都通過不同的屬性或方法以不同的方式處理。 這具有明確性的優勢,我之前稱讚過,但是這種明確性並沒有真正為我們帶來任何好處。 當你使用命名空間模塊時,這個 API 只會變得更難使用。 相比之下,Vuex 5 看起來就像你通常希望的那樣工作:
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
所有的東西——狀態、getter 和動作——都可以直接在 store 的根目錄下使用,使用起來很簡單,而且省去了很多冗長,實際上消除了對選項 API 或編寫使用mapState
、 mapGetters
、 mapActions
和mapMutations
的所有需要用於組合 API 的額外computed
語句或簡單函數。 這只是使 Vuex 商店的外觀和行為就像您自己構建的普通商店一樣,但它獲得了插件、調試工具、官方文檔等的所有好處。
組成商店
我們今天要看的 Vuex 5 的最後一個方面是可組合性。 Vuex 5 沒有可以從單個商店訪問的命名空間模塊。 這些模塊中的每一個都將被拆分為一個完全獨立的商店。 對於組件來說,這很簡單:他們只需導入他們需要的任何商店,然後啟動並使用它們。 但是,如果一家商店想與另一家商店互動怎麼辦? 在 v4 中,命名空間使整個事情複雜化,因此您需要在commit
和dispatch
調用中使用命名空間,使用rootGetters
和rootState
,然後逐步進入要從中訪問 getter 和狀態的命名空間。 這是它在 Vuex 5 中的工作方式:
// store/greeter.js import { defineStore } from 'vuex' export default defineStore({ name: 'greeter', state () { return { greeting: 'Hello' } } }) // store/counter.js import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore({ name: 'counter', // Then `use` the store use () { return { greeter: greeterStore } }, state () { return { count: 0 } }, getters: { greetingCount () { return `${this.greeter.greeting} ${this.count}' // access it from this.greeter } } })
使用 v5,我們導入我們希望使用的商店,然後使用use
註冊它,現在它可以在整個商店中以您提供的任何屬性名稱訪問。 如果您使用商店定義的組合 API 變體,事情會更簡單:
// store/counter.js import { ref, computed } from 'vue' import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore('counter', ({use}) => { // `use` is passed in to function const greeter = use(greeterStore) // use `use` and now you have full access const count = 0 const greetingCount = computed(() => { return `${greeter.greeting} ${this.count}` // access it like any other variable }) return { count, greetingCount } })
沒有更多的命名空間模塊。 每個商店都是獨立的,單獨使用。 您可以使用use
使一個商店在另一個商店中可用以組合它們。 在這兩個示例中, use
基本上與之前的vuex.store
機制相同,它們確保我們使用正確的 Vuex 實例來實例化存儲。
打字稿支持
對於 TypeScript 用戶來說,Vuex 5 最大的優點之一是簡化使得向所有內容添加類型變得更加簡單。 舊版本的 Vuex 幾乎不可能實現的抽象層,現在,使用 Vuex 4,它們增加了我們使用類型的能力,但是仍然需要大量的手動工作才能獲得相當數量的類型支持,而在 v5 中,您可以將您的類型內聯,就像您希望和期望的那樣。
結論
Vuex 5 看起來幾乎完全符合我(可能還有其他許多人)所希望的那樣,而且我覺得它來得還不夠快。 它簡化了 Vuex 的大部分內容,消除了一些相關的精神開銷,並且只會在增加靈活性的地方變得更加複雜或冗長。 在下面留下評論,說明您對這些更改的看法以及您可能會做出哪些改變或另外做出哪些改變。 或者直接去源頭添加一個 RFC(Request for Comments)到列表中,看看核心團隊的想法。