Vuex 是不是有點繁瑣?
Vuex 是針對 Vue2 來設計的,因為 option API 本身有很多缺點,所以 Vuex 只好做各種補丁彌補這些缺點,于是變得比較“復雜”。
現(xiàn)在 Vue3 推出了Composition API,功能更強大也彌補了之前的缺點,但是 Vuex 4.0 只是兼容了 Vue3,使用風格上似乎沒啥變化。
于是乎怎么看怎么別扭,不是說 Vuex 不夠強大,Vuex 的 state 也使用了 reactive ,而且也是用 provide/inject 實現(xiàn)注入的,但是沒有后續(xù)了,Composition API 的其他特性呢?感覺好浪費呀。
雖然也可以基于 Vuex 用 compositionAPI 的方式實現(xiàn)功能,但是總感覺有點大炮打蚊子的感覺。
做一個輕量級狀態(tài)管理。
按照“自己動手豐衣足食”的原則,我們自己來做一個輕量級的狀態(tài)管理。
模仿 Vuex 試著實現(xiàn)了一下基本功能,有點理解為啥 Vuex 弄得那么繞了,因為要把操作函數(shù)也給包含進去確實有點難度。
那么就輕量到底吧,只包含狀態(tài),不包括 mutations、action 這些操作函數(shù)。
于是功能變成這個樣子:

狀態(tài):全局狀態(tài)、局部狀態(tài)
功能:初始化狀態(tài)、指定的組件里注入局部狀態(tài)、子組件里加載局部狀態(tài)
緩存功能,就是可以把狀態(tài)存入localstorage里面保存,以及初始化的時候從localstorage里面加載狀態(tài)。
緩存功能暫時沒有實現(xiàn),還沒想好局部狀態(tài)的緩存方案。
功能簡單,寫起來也就容易多了,然后順便做成插件的形式,便于使用。
// 模仿Vuex寫一個簡單的數(shù)據(jù)、狀態(tài)、緩存管理
import { reactive, provide, inject } from 'vue'
export default {
// 狀態(tài)容器
store: {
state: {}, // 全局狀態(tài)
init: () => {}, // 初始化全局狀態(tài)
reg: {}, // 注冊局部狀態(tài)
get: {} // 獲取局部狀態(tài)
},
// 用 symbol 做個標識,避免重名
storeFlag: Symbol('VuexDataState'),
// 創(chuàng)建安裝插件的實例
createStore(info) {
/* info 的結構示例
const _info = {
// 全局狀態(tài),在main.js里面注入
global: {
blogState: { // 每個狀態(tài)都必須是對象,不支持基礎類型
aaa: '狀態(tài)演示'
}
},
// 局部狀態(tài),需要手動注入
local: {
dataList() {
return {}
}
},
// 初始化函數(shù),可以從后端、前端等獲取數(shù)據(jù)加入狀態(tài)
// 注入后被動調用,僅限于全局狀態(tài)
init(state) {}
}
*/
for (const key in info.global) {
// 把全局狀態(tài)存入state
this.store.state[key] = reactive(info.global[key])
}
for (const key in info.local) {
const localKey = Symbol(key)
// 加上注冊函數(shù)
this.store.reg[key] = () => {
// 把局部狀態(tài)變成 reactive 的形式
const state = reactive(info.local[key]())
// 注入
provide(localKey, state)
// 返回狀態(tài),
return state
}
this.store.get[key] = () => {
// 把局部狀態(tài)變成 reactive 的形式
const state = inject(localKey)
// 返回狀態(tài),
return state
}
}
// 加上初始化函數(shù)
if (typeof info.init === 'function') {
this.store.init = info.init
}
const _store = this.store
const _storeFlag = this.storeFlag
return {
// 安裝插件
install (app, options) {
// console.log('install--我的狀態(tài)', _store)
// 注入狀態(tài),用 symbol 作為標記,避免重名,避免外部直接用 inject 獲取
app.provide(_storeFlag, _store)
// 設置模板使用狀態(tài)
app.config.globalProperties.$state = _store.state
// 調用初始化,給全局狀態(tài)賦值
_store.init(_store.state)
}
}
},
// 代碼里面調用
useStore() {
// 獲取全局狀態(tài)
const { state, reg, get } = inject(this.storeFlag)
return {
state, // 返回全局狀態(tài)
reg, // 注冊局部狀態(tài)的函數(shù),并且返回對應的局部狀態(tài)
get // 子組件里面獲取狀態(tài)
}
}
}
怎么樣,夠輕吧,不超過一百行代碼,如果去掉注釋空行的話,大概也就三十多行吧。
reactive, provide, inject
狀態(tài)要實現(xiàn)響應性,那當然要做成 reactive 形式的。
provide、inject 實現(xiàn)注入功能。store
內部狀態(tài)容器。
state:狀態(tài)
init:全局狀態(tài)的初始化的函數(shù)
reg:局部狀態(tài)的注入函數(shù)
get:獲取局部狀態(tài)的函數(shù)storeFlag
用 symbol 做全局狀態(tài)的標記,避免重名。是不是有一種高大上的感覺?[狗頭]useStore
是不是眼熟,在代碼里面獲取全局狀態(tài)的。
除了返回全局狀態(tài)外,還可以返回局部狀態(tài)的注入函數(shù)和獲取函數(shù)。
因為使用 symbol 作為key,外部無法獲取,所以需要內部提供一個函數(shù)。(我不會告訴你我是故意的)
如果想把狀態(tài)變成只讀(readonlyReactive)的形式然后在返回,那么可以在這里操作。
- 組件里的使用方法
import VueDS from 'vue-data-state'
const { state, reg, get } = VueDS.useStore()
state // 全局狀態(tài)
// 父組件注入狀態(tài),并且返回局部狀態(tài),以便父組件使用
const 局部狀態(tài) = reg.局部狀態(tài)名稱()
// 子組件獲取局部狀態(tài)
const 局部狀態(tài) = get.局部狀態(tài)名稱()
createStore
看著是不是眼熟,功能和 Vuex 的 createStore 是一樣的,接收參數(shù)創(chuàng)建 store 然后通過插件注入到 vue 的app上面。
函數(shù)返回 install,用于安裝插件。_info
這個沒啥用,就是介紹一下參數(shù)的屬性格式,實現(xiàn)代碼的時候看著方便。另外去掉注釋就可以做測試用。第一個for
遍歷全局狀態(tài),變成 reactive 掛到 store 里面。第二個 for
遍歷局部狀態(tài),變成注入和獲取的函數(shù),掛到 reg 和 get 里面。
這個 provide 的 key 也采用 symbol 的形式,避免重名。init
把初始化函數(shù)掛上。install
安裝插件,按照 Vue 官網(wǎng)示例,寫了這個install。
對了,我只是把全局狀態(tài)掛到模板上面了,局部狀態(tài)沒有掛呢。
局部狀態(tài)似乎掛不上,還需要再考慮考慮。
先安裝資源包
yarn add vue-data-state
定義狀態(tài)
// /store-ds/index.js
import VuexDataState from 'vue-data-state'
export default VuexDataState.createStore({
global: { // 全局狀態(tài)
userInfo: {
name:'當前登錄人'
}
},
local: { // 局部狀態(tài)
// 數(shù)據(jù)列表,使用前需要先注冊
dataListState() { // 顯示博文列表用的狀態(tài)
return {
findKind: {}, // 查詢方式
find: {}, // 查詢關鍵字
page: { // 分頁參數(shù)
pageTotal: 100,
pageSize: 2,
pageIndex: 1,
orderBy: { id: false }
},
_query: {}, // 緩存的查詢條件
isReload: false // 重新加載數(shù)據(jù),需要統(tǒng)計總數(shù)
}
}
},
// 可以給全局狀態(tài)設置初始狀態(tài),可以是異步操作
init(state) {
setTimeout(() => {
state.blogState.name = 'int里面設置的數(shù)據(jù),可以異步'
},3000)
}
})
global
全局狀態(tài),每一個狀態(tài)都必須是對象(包含數(shù)組)的形式,不能是基礎類型。
全局狀態(tài),會默認注入到根 app 里面。
狀態(tài)名稱、屬性名稱可以隨意,這里只是舉個例子。local
局部狀態(tài),每個狀態(tài)也必須是對象形式,不會默認注入,需要在父組件里面使用 reg 調用函數(shù)才能注入。
需要使用 return 的形式,原理和 data 一樣。Vuex 模塊里的 state 也是需要用 return 形式的。
狀態(tài)名稱、屬性名稱可以隨意,這里只是舉個例子。init
初始化全局狀態(tài)的函數(shù),可以不設置。
在main.js里面安裝插件時,注入全局狀態(tài)后 init會被調用,這時候可以給全局狀態(tài)賦值,支持異步操作。
在main.js 里面使用
這個就和 Vuex 一樣了:
main.js
import { createApp } from 'vue'
import store from './store-ds' // 輕量級狀態(tài)
createApp(App)
.use(store) // 輕量級狀態(tài)
后續(xù)會在個人博客里面試用一下,具體使用的時候,才會發(fā)現(xiàn)有沒有問題,以及如何改進。
FAQ
傳說中的跟蹤呢?
關于跟蹤的問題,一直理解的不深刻,因為dev-tool總是安裝不上,后來好不容易安裝上了,卻不工作。所以暫時跳過這個功能。Vuex支持插件,你的這個呢?
這個說起來有點復雜,簡單的說,目前還沒有這樣的需求,所以就先跳過了,以后需要的話,可以再加嘛。
要做插件的話也簡單,用 watch 對狀態(tài)做深度監(jiān)聽,然后調用插件鉤子就行。
要不然我為啥要把狀態(tài)拆開做reactive呢?不是說不讓直接修改狀態(tài)嗎?
關于這一點也是比較復雜。
我可以把狀態(tài)做成只讀的,readonlyReactive一下就行,然后再設計 類似 mutations 的方法 來修改狀態(tài)。
但是這么做的意義到底是什么呢?
沒有實現(xiàn)跟蹤功能,也沒用插件,也不知道怎么弄到dev-tool里面去。
這些都是配套工程,如果沒有這些配套工程,只是做一個只讀的話,總是感覺怪怪的。為啥要弄個局部狀態(tài)?
這個要從一次討論說起。
某天和知乎大神聊天,他說要做一個模塊內的共享狀態(tài),一開始我還不理解,討論了半天,感覺大神說的確實在理。
于是慢慢開始嘗試,最后發(fā)現(xiàn)確實挺香的。具體的會在后面的博客項目里面介紹。支持 option API嗎?
一開始忘記這個事了,后來才想起來,因為是專門針對composition API來設計的,所以應該是不支持的吧。
源碼
https://gitee.com/naturefw/vue-data-state