打造 Vue 技术栈中的“时间宝石“

现代浏览器的功能愈来愈强大,前端须要处理的业务逻辑也愈来愈复杂,提供良好的交互是咱们一直追求的事,而咱们在作的可视化报表工具,有一个重要的提高用户体验的功能,撤销 & 重作,这个功能给用户以安全感和保障,用户不会担忧所作的操做以及交互会消失掉,不可追溯。前端

为了实现这个功能,我调研了一些实现方式,有基于 Immutable 数据结构的,有基于 数据结构去管理的,咱们的实际项目使用 Vuex 做为全局状态管理工具。而真正适合咱们实际项目的实际上是基于 Vuex 的 store 实例API的实现方式,将全部经过 Vuex 存储的状态,在触发 actions/mutations 时存为历史记录,当咱们想用的时候就可随意“穿梭”。vue

鉴于此我结合 stateshot 开发了一个能够 “时间旅行” 的 Vuex 插件。git

本文将介绍一种基于 Vuex API 实现的 “时间旅行” 插件,以动态网格布局为例子,使用 stateshot.js 实现的 撤销 & 重作 功能,大体效果以下图:github

vuex-stateshot-gif

时间旅行

Why

time-stone-w498

Time Gem,强大的时间宝石,拥有操控时间的能力。vuex

When

deadpoo

后悔,人类很神奇的感受,当我作了一件事以后后悔了,想若是没作该多好,想要回到当初。npm

How

infinity wa

那么咱们须要一颗拥有时间旅行强大功能的“时间宝石”,让咱们去创造它api

实现“时间宝石“

vuex-stateshot 的实现借助了 Vuex 的一些API,以及 stateshot.js 用来记录历史状态,以及进行撤销 & 重作。浏览器

202001_vuex-stateshot

当咱们触发一个 actions/mutations,能够经过订阅的方式,触发一次 snapshot,记录下历史状态快照,这样就方便咱们进行撤销 & 重作。安全

下面让咱们认识一下这些API,subscribesubscribeActionregisterModulecreateNamespacedHelpersbash

Vuex API

Vuex.Store 实例方法以及辅助函数中提供了一些可能平时用不到的 API,这些 API 在开发 Vuex 插件很好用。

store.subscribe

订阅 store 的 mutation。handler 会在每一个 mutation 完成后调用,接收 mutation 和通过 mutation 后的状态做为参数:

store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})
复制代码

store.subscribeAction

3.1.0 起,subscribeAction 提供了经常使用于开发 Vuex 插件的用法,能够指定订阅处理函数的被调用时机应该在一个 action 分发以前(before)仍是以后(after) (默认行为是以前):

store.subscribeAction({
  before: (action, state) => {
    console.log(`before action ${action.type}`)
  },
  after: (action, state) => {
    console.log(`after action ${action.type}`)
  }
})
复制代码

store.registerModule

在 store 建立以后,你可使用 store.registerModule 方法注册模块:

// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})
复制代码

模块动态注册功能使得其余 Vue 插件能够经过在 store 中附加新模块的方式来使用 Vuex 管理状态。

createNamespacedHelpers

经过使用 createNamespacedHelpers 建立基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}
复制代码

使用

vuex-stateshot 插件的使用方式是无侵入的,可插拔的,插件经过 registerModule API动态建立了名为 vuexstateshot 的命名空间,来存储一些时间旅行须要用到的状态、方法。

安装

能够经过以下命令安装:

npm i vuex-stateshot -S
or
yarn add vuex-stateshot -S
复制代码

建立插件

在建立插件(createPlugin)的时候能够指定有哪些模块(MODULE__NAME)以及模块下的哪些 actions/mutations 须要订阅,可选择性地传入 stateshot 的History Options API

一个栗子🌰

import { createPlugin } from 'vuex-stateshot'

const subscribes = {
  // The special root module key
  rootModule: {
    // The actions you want snapshot
    actions: [],
    // The mutations you want snapshot
    mutations: []
  },
  // The custom module name
  __MODULE__NAME__: {
    // The actions you want snapshot
    actions: [],
    // The mutations you want snapshot
    mutations: []
  }
}

const options = {
  maxLength: 20
}

const store = new Vuex.Store({
  state: {},
  ...,
  plugins: [createPlugin(subscribes, options)]
})
复制代码

组件内部使用

在组件内部,能够经过 createNamespacedHelpers API,指定插件的命名空间 vuexstateshot 来映射组件绑定辅助函数

import { createNamespacedHelpers } from 'vuex'

const { mapGetters, mapActions } = createNamespacedHelpers('vuexstateshot')

export default {
  ...,

  computed: {
    ...mapGetters([ 'undoCount', 'redoCount', 'hasUndo', 'hasRedo' ])
  },

  methods: {
    ...mapActions(['undo', 'redo', 'reset'])
  }
}
复制代码

Undo/Redo 方法

经过组件绑定辅助函数 mapActions,咱们能够获得 undo/redo 方法,用来管理状态。

方法名 描述 回调
undo 若是有可撤销的历史记录,则能够获得上一个记录的状态 () => prevState
redo 当执行过 undo 后,能够经过 redo 获取到最近一次 undo 过的历史记录 () => nextState
reset 清除历史记录 -

历史记录状态

经过组件绑定辅助函数 mapGetters,咱们能够获得 hasUndohasRedoundoCountredoCount 等状态,用来逻辑处理。

当触发一次状态同步, 此时 undoCount = 1/ hasUndo = true; 这是使用插件的一个开端; 当你调用一次 undo 后, 会有一次 redo 记录

状态 描述 类型 初始值
undoCount 能够撤销的历史记录计数. Number 0
redoCount 能够重作的历史记录技术 Number 0
hasUndo 是否能够撤销 Boolean false
hasRedo 是否能够重作 Boolean false

任意门

travel-doo

当需求复杂时,可能我想撤销的不是一次 actions/mutations,而是须要撤销若干个 actions/mutations,对于这种需求,vuexstateshot 提供了自定义时机同步历史记录的方法,让屡次复杂的操做“一键还原”。

Methods

名称 描述 回调
syncState 自定义方法同步历史记录快照 -
unsubscribeAction 中止订阅 Actions -
subscribeAction 从新订阅 Actions,一般搭配 unsubscribeAction 使用 -
unsubscribe 中止订阅 Mutations -
subscribe 从新订阅 Mutations,一般搭配 unsubscribe 使用 -

一个场景

假设咱们订阅了 changeThemechangeColorchangeLang 三个Actions,每一个 Action 触发的时候,都会记录一次历史记录快照,可实际场景的需求是,须要这些状态所有改变后才同步历史记录快照,以便撤销时还原多个状态。

import { mapActions } from 'vuex'

export default {
  name: 'xxx',
  ...
  methods: {
    ...mapActions([
      'changeTheme',
      'changeColor',
      'changeLang'
    ]),
    handleChange () {
      // 中止订阅 Actions
      this.$stateshot.unsubscribeAction()
      // 屡次触发已订阅的 Actions
      this.changeTheme('dark')
      this.changeColor('#fa4')
      this.changeTheme('zh')
      // 从新订阅 Actions
      this.$stateshot.subscribeAction()
      // 同步历史记录快照
      this.$stateshot.syncState()
    }
  }
}
复制代码

Tips: vuex-stateshot 同时提供了能够中止订阅 Actions/Mutations 的方法

在线Demo

Edit Vuex Stateshot

结语

在可视化工具项目中,咱们已经在使用 vuex-stateshot 来管理历史状态了。性能表现良好,完美达成了“时间旅行”的需求。为用户提供了操做交互上的安全保障

感谢 @doodlewind 提供了出色的工具 stateshot 以及 Vuex 3.1.0+ 提供的 subscribeAction API,让操做变得有序可依。

vuex-stateshot 插件特性✨:

  • 无侵入、可插拔的插件调用
  • 严谨的逻辑业务状态
  • 稳定的撤销 & 重作方法
  • 任意时机同步快照的方法
  • 100% 测试场景覆盖率

资源

原文:xlbd.me/create-a-ti…

相关文章
相关标签/搜索