現代瀏覽器的功能愈來愈強大,前端須要處理的業務邏輯也愈來愈複雜,提供良好的交互是咱們一直追求的事,而咱們在作的可視化報表工具,有一個重要的提高用戶體驗的功能,撤銷 & 重作,這個功能給用戶以安全感和保障,用戶不會擔憂所作的操做以及交互會消失掉,不可追溯。前端
爲了實現這個功能,我調研了一些實現方式,有基於 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
插件特性✨:
原文: http://xlbd.me/create-a-time-...