現代瀏覽器的功能愈來愈強大,前端須要處理的業務邏輯也愈來愈複雜,提供良好的交互是咱們一直追求的事,而咱們在作的可視化報表工具,有一個重要的提高用戶體驗的功能,撤銷 & 重作,這個功能給用戶以安全感和保障,用戶不會擔憂所作的操做以及交互會消失掉,不可追溯。前端
爲了實現這個功能,我調研了一些實現方式,有基於 Immutable 數據結構的,有基於棧 數據結構去管理的,咱們的實際項目使用 Vuex 做爲全局狀態管理工具。而真正適合咱們實際項目的實際上是基於 Vuex 的 store
實例API的實現方式,將全部經過 Vuex 存儲的狀態,在觸發 actions
/mutations
時存爲歷史記錄,當咱們想用的時候就可隨意「穿梭」。vue
鑑於此我結合 stateshot
開發了一個能夠 「時間旅行」 的 Vuex 插件。git
本文將介紹一種基於 Vuex API 實現的 「時間旅行」 插件,以動態網格佈局爲例子,使用 stateshot.js
實現的 撤銷 & 重作 功能,大體效果以下圖:github
Time Gem
,強大的時間寶石,擁有操控時間的能力。vuex
後悔,人類很神奇的感受,當我作了一件事以後後悔了,想若是沒作該多好,想要回到當初。npm
那麼咱們須要一顆擁有時間旅行強大功能的「時間寶石」,讓咱們去創造它api
vuex-stateshot
的實現藉助了 Vuex 的一些API,以及 stateshot.js
用來記錄歷史狀態,以及進行撤銷 & 重作。瀏覽器
當咱們觸發一個 actions
/mutations
,能夠經過訂閱的方式,觸發一次 snapshot
,記錄下歷史狀態快照,這樣就方便咱們進行撤銷 & 重作。安全
下面讓咱們認識一下這些API,subscribe
、subscribeAction
、registerModule
、createNamespacedHelpers
bash
Vuex.Store 實例方法以及輔助函數中提供了一些可能平時用不到的 API,這些 API 在開發 Vuex 插件很好用。
訂閱 store 的 mutation。handler
會在每一個 mutation 完成後調用,接收 mutation 和通過 mutation 後的狀態做爲參數:
store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
複製代碼
從 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 建立以後,你可使用 store.registerModule
方法註冊模塊:
// 註冊模塊 `myModule`
store.registerModule('myModule', {
// ...
})
// 註冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
複製代碼
模塊動態註冊功能使得其餘 Vue 插件能夠經過在 store 中附加新模塊的方式來使用 Vuex 管理狀態。
經過使用 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'])
}
}
複製代碼
經過組件綁定輔助函數 mapActions
,咱們能夠獲得 undo
/redo
方法,用來管理狀態。
方法名 | 描述 | 回調 |
---|---|---|
undo | 若是有可撤銷的歷史記錄,則能夠獲得上一個記錄的狀態 | () => prevState |
redo | 當執行過 undo 後,能夠經過 redo 獲取到最近一次 undo 過的歷史記錄 |
() => nextState |
reset | 清除歷史記錄 | - |
經過組件綁定輔助函數 mapGetters
,咱們能夠獲得 hasUndo
、hasRedo
、undoCount
、redoCount
等狀態,用來邏輯處理。
當觸發一次狀態同步, 此時
undoCount = 1
/hasUndo = true
; 這是使用插件的一個開端; 當你調用一次undo
後, 會有一次redo
記錄
狀態 | 描述 | 類型 | 初始值 |
---|---|---|---|
undoCount | 能夠撤銷的歷史記錄計數. | Number | 0 |
redoCount | 能夠重作的歷史記錄技術 | Number | 0 |
hasUndo | 是否能夠撤銷 | Boolean | false |
hasRedo | 是否能夠重作 | Boolean | false |
當需求複雜時,可能我想撤銷的不是一次 actions
/mutations
,而是須要撤銷若干個 actions
/mutations
,對於這種需求,vuexstateshot
提供了自定義時機同步歷史記錄的方法,讓屢次複雜的操做「一鍵還原」。
Methods
名稱 | 描述 | 回調 |
---|---|---|
syncState | 自定義方法同步歷史記錄快照 | - |
unsubscribeAction | 中止訂閱 Actions |
- |
subscribeAction | 從新訂閱 Actions ,一般搭配 unsubscribeAction 使用 |
- |
unsubscribe | 中止訂閱 Mutations |
- |
subscribe | 從新訂閱 Mutations ,一般搭配 unsubscribe 使用 |
- |
一個場景
假設咱們訂閱了
changeTheme
、changeColor
、changeLang
三個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
的方法
在可視化工具項目中,咱們已經在使用 vuex-stateshot
來管理歷史狀態了。性能表現良好,完美達成了「時間旅行」的需求。爲用戶提供了操做交互上的安全保障
感謝 @doodlewind 提供了出色的工具 stateshot
以及 Vuex 3.1.0+ 提供的 subscribeAction
API,讓操做變得有序可依。
vuex-stateshot
插件特性✨: