打造 Vue 技術棧中的「時間寶石「

doctor-strange-time-stone.jpg

現代瀏覽器的功能愈來愈強大,前端須要處理的業務邏輯也愈來愈複雜,提供良好的交互是咱們一直追求的事,而咱們在作的可視化報表工具,有一個重要的提高用戶體驗的功能,撤銷 & 重作,這個功能給用戶以安全感和保障,用戶不會擔憂所作的操做以及交互會消失掉,不可追溯。前端

爲了實現這個功能,我調研了一些實現方式,有基於 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% 測試場景覆蓋率

資源

原文: http://xlbd.me/create-a-time-...
相關文章
相關標籤/搜索