我的状态管理模式
什么是“状态管理模式”?
Vuex官方解释:把组件的共享状态抽取出来,以一个全局单例模式管理即为状态管理模式。 在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
为什么要封装 dl-store
封装 dl-store
的初衷是我在自己封装的自定义表单组件中想使用状态管理模式,却不想依赖 Vuex
。所以就想着以 Vuex
为参考来封装一个我自己的状态管理模式,毕竟所谓的状态管理模式不就是一个专门存放数据、处理数据的大对象么!
class DlStore {
constructor(config = {}) {
this.state = config.state || {}
}
}
const store = new DlStore({
state: {
apples: 6
}
})
store.state.apples // -> 6
收工,下班~~~
hold on hold on,Vuex
还有几大核心概念呢,dl-store
肯定也是要“抄”的。
我拿起键盘就是一梭子,几大核心干起来🐱👤
核心概念
State
为了控制 state
中的数据不可被随意修改,通过 es6
的 proxy
对象来代理 state
数据。
class InitState {
constructor() {
// ...
}
createProxy(data) {
if (typeof data !== 'object') return data
return new Proxy(data, {
get: (target, key) => {
return target[key]
},
set: (target, key, val) => {
if (!this.actionState) {
throw new Error('set state data must use action')
}
target[key] = this.createProxy(val)
return true
},
deleteProperty: (target, key) => {
if (!this.actionState) {
throw new Error('delete state data must use action')
}
Reflect.deleteProperty(target, key)
return true
}
})
}
}
通过上面的代码可以发现 this.actionState
是一个内部的属性,只有在 Action
中触发 this.actionState
状态的改变才可以修改 state
数据,否则将会抛出异常。
Action
dl-store
中没有 mutation
,更改 store
中数据的唯一方法是通过提交 action
。每个 action
都有一个字符串的事件类型 (type
)和一个回调函数 (handler
)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 context
对象作为第一个参数,且 action
内允许异步操作。
你不能直接调用一个 action
处理函数。要唤醒一个 action
处理函数,你需要以相应的 type
调用 store.dispatch
方法。
Getters
Getters
是 store
的计算属性。getter
的返回值会根据它的依赖被缓存起来,当 getter
所依赖的 state
数据发生变化时,getter
才会重新计算。
getter
计算的核心原理是,先将 getter
函数执行一遍,通过 state
的 proxy
的 get
拦截,为 state
的属性添加 getter
的监听依赖并将 getter
函数缓存。后续如果 state
数据变化,getter
也就会及时响应了。
getters.js
class InitGetter {
constructor() {
// ...
}
init() {
for (const [key, handler] of Object.entries(this.gettersObj)) {
if (typeOf(handler) !== 'function') {
throw new Error(`${key} of getters must be a function`)
}
if (Reflect.has(parentStore.state, handler)) {
throw new Error(`state already has param named ${key}`)
}
this.getterState = true
this.getterStateKey = key
// 执行计算函数
const getterResult = handler({
state: parentStore.state,
getters: parentStore.getters,
rootState: this.store.state,
rootGetters: this.store.getters
})
// 存储计算结果
Watcher.dependences.filter(fn => fn.key === `${key}-getter-fn`)
.forEach(fn => {
fn.lastRes = getterResult
})
// ...
this.getterStateKey = ''
this.getterState = false
}
}
// 生成有记忆的计算函数
gennerateMemoryFn(lastVal) {
const _that = this
const key = this.getterStateKey
const handler = function(val) {
if (lastVal === val) {
return handler.lastRes
}
lastVal = val
// 重新计算
const fn = _that.gettersObj[key]
const res = fn({
state: _that.parentStore.state,
getters: _that.parentStore.getters,
rootState: _that.store.state,
rootGetters: _that.store.getters
})
handler.lastRes = res
Reflect.set(_that.gettersData, key, res)
// 触发 观察者
const wactch = _that.parentStore.__watcherMap__.get(key)
wactch && wactch.emit(res)
return res
}
handler.key = `${key}-getter-fn`
return handler
}
}
state.js
class InitState {
constructor() {
// ...
}
createProxy(data) {
if (typeof data !== 'object') return data
return new Proxy(data, {
get: (target, key) => {
const value = target[key]
const wactchKey = parentKey ? `${parentKey}.${key}` : key
const w = watcherMap.has(wactchKey)
? watcherMap.get(wactchKey)
: new Watcher(wactchKey, parentStore)
// 为 getters 添加观察者
if (parentStore.__getter__.getterState) {
const fn = parentStore.__getter__.gennerateMemoryFn(value)
w.add(fn)
watcherMap.set(wactchKey, w)
}
return value
},
set: (target, key, val) => {
if (!this.actionState) {
throw new Error('set state data must use action')
}
target[key] = this.createProxy(val)
// 触发 观察者
const wactchKey = parentKey ? `${parentKey}.${key}` : key
const w = watcherMap.has(wactchKey)
? watcherMap.get(wactchKey)
: new Watcher(wactchKey, parentStore)
w.emit(val, oldVal)
watcherMap.set(wactchKey, w)
return true
},
deleteProperty: (target, key) => {
if (!this.actionState) {
throw new Error('delete state data must use action')
}
Reflect.deleteProperty(target, key)
// 触发 观察者
const wactchKey = parentKey ? `${parentKey}.${key}` : key
const w = watcherMap.has(wactchKey)
? watcherMap.get(wactchKey)
: new Watcher(wactchKey, parentStore)
w.emit(val, oldVal)
watcherMap.set(wactchKey, w)
return true
}
})
}
}
Watch
watch
是 dl-store
中新增的概念,参考了 vue.js
的 watch
体验, 是 store
中可以自定义的侦听器对象。通过监听及时响应数据的变化,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch
是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 Store
实例将会在实例化时遍历 watch
对象的每一个 property
,并为每一个 property
添加观察函数。
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store
对象就有可能变得相当臃肿。
为了解决以上问题,dl-store
允许我们将 store
分割成模块(module
)。每个模块拥有自己的 state
、actions
、getters
、watch
:
const moduleA = {
state: () => ({ ... }),
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.modules.a.state // -> moduleA 的状态
store.modules.b.state // -> moduleB 的状态
通过 store.modules[moduleName]state
可以查找到子模块的状态,但是这样获取状态的链路较长。 store
将模块的状态以如下规则映射到 store
身上:
store.__[moduleName]_state__
:子模块的状态树。store.__[moduleName]_getters__
:子模块的计算集合。store.__[moduleName]_dispatch__
:子模块的dispatch
方法。store.__[moduleName]_addWatch__
:子模块的addWatch
方法。store.__[moduleName]_removeWatch__
:子模块的removeWatch
方法。
在 store 创建之后,你可以使用 store.registerModule 方法动态的注册模块:
store.registerModule('fruits', fruitsModule)
History
__history__
也是 dl-store
新增的特性,是记录 store
中操作 state
的时间线数据队列,最多缓存100次操作。
通过 __history__
对象的 prevTime()
/nextTime()
方法,可以轻松实现 state
数据的撤销/回退功能。
总结
dl-store
的设计思路是参考的 vuex,所以使用体验上也极为相似。同时结合了 vue.js 的底层原理实现数据的响应式监听。如果有感兴趣的同学,欢迎一起学习交流~~
传送门
转载自:https://juejin.cn/post/7181386217013903418