likes
comments
collection
share

我的状态管理模式

作者站长头像
站长
· 阅读数 43

什么是“状态管理模式”?

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 中的数据不可被随意修改,通过 es6proxy 对象来代理 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

Gettersstore 的计算属性。getter 的返回值会根据它的依赖被缓存起来,当 getter 所依赖的 state 数据发生变化时,getter 才会重新计算。

getter 计算的核心原理是,先将 getter 函数执行一遍,通过 stateproxyget 拦截,为 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

watchdl-store 中新增的概念,参考了 vue.jswatch 体验, 是 store 中可以自定义的侦听器对象。通过监听及时响应数据的变化,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。 watch 是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 Store 实例将会在实例化时遍历 watch 对象的每一个 property,并为每一个 property 添加观察函数。

Modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,dl-store 允许我们将 store 分割成模块(module)。每个模块拥有自己的 stateactionsgetterswatch

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
评论
请登录