vuex實(shí)現(xiàn)歷史記錄的示例代碼
最近自研著一個(gè)可視化操作平臺(tái),其中涉及到用戶操作后可撤銷(xiāo)或重做,在網(wǎng)上搜了一些解決思路,完善自己所設(shè)想的解決思路。
歷史記錄需求的要點(diǎn)
- 可存儲(chǔ)在 localStorage 中
- 可多次撤銷(xiāo)或多次重做
- 點(diǎn)擊列表中的一項(xiàng),將歷史倒退或前進(jìn)至指定位置
看似簡(jiǎn)單的需求,在基礎(chǔ)建設(shè)設(shè)計(jì)上的錯(cuò)誤,亦會(huì)在未來(lái)導(dǎo)致更多的工作量。所以結(jié)合上面兩點(diǎn)的要求,發(fā)現(xiàn) vuex 的基本思路非常適合完成這個(gè)需求,redux 同樣。
實(shí)現(xiàn)思路
此項(xiàng)目用了 typescript 來(lái)加強(qiáng)代碼的嚴(yán)謹(jǐn)性,方便日后維護(hù),大家簡(jiǎn)單看個(gè)思路。
1. 先定義歷史記錄的數(shù)據(jù)結(jié)構(gòu)
interface HistoryItem {
timestrap: number; // 記錄時(shí)間戳
name: string; // 記錄名稱
redo: string; // 重做Mutation
undo: string; // 撤銷(xiāo)Mutation
redoParams: any[]; // 重做Mutation提交參數(shù)
undoParams: any[]; // 撤銷(xiāo)Mutation提交參數(shù)
}
interface HistoryStatus {
historys: HistoryItem[]; // 記錄history數(shù)組
_currentHistory: number; // 當(dāng)前節(jié)點(diǎn)索引
}
2. 編寫(xiě) History 狀態(tài)模塊
編寫(xiě)基礎(chǔ)操作history狀態(tài)的vuex module,創(chuàng)建記錄的Mutation,重做和撤銷(xiāo)的Action
一條記錄是包含對(duì)這個(gè)步驟的執(zhí)行redo操作與撤銷(xiāo)undo操作的。所以在用戶點(diǎn)擊列表其中一項(xiàng)時(shí),應(yīng)該是循環(huán)回退到當(dāng)前項(xiàng)的前一項(xiàng)undo,或循環(huán)redo到當(dāng)前項(xiàng)
所以需要增加一條空記錄,方便用戶點(diǎn)擊空記錄撤銷(xiāo)最初的操作。

運(yùn)用了vuex-module-decorators 裝飾器,寫(xiě)更易維護(hù)的代碼
import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";
@Module({ namespaced: true })
export class HistoryModule extends VuexModule<HistoryStatus> implements HistoryStatus {
/**
* 初始化一個(gè)空記錄的原因主要是方便列表操作時(shí):
* 當(dāng)用戶點(diǎn)擊最早的一條記錄時(shí),可以正常撤銷(xiāo)用戶操作的第一步
**/
public historys: HistoryItem[] = [
{
name: `打開(kāi)`,
timestrap: Date.now(),
redo: "",
redoParams: [],
undo: "",
undoParams: [],
},
];
public _currentHistory: number = 0;
// getter
get current(){
return this._currentHistory;
}
// getter
get historyList(): HistoryItem[] {
return this.historys || [];
}
// 創(chuàng)建歷史記錄
@Mutation
public CREATE_HISTORY(payload: HistoryItem) {
if (this._currentHistory < this.historys.length - 1) {
this.historys = this.historys.slice(0, this._currentHistory);
}
// 由于js的深淺拷貝問(wèn)題,所以在創(chuàng)建時(shí)都需要對(duì)數(shù)據(jù)進(jìn)行深拷貝
// 想嘗試lodash的clone函數(shù),但發(fā)現(xiàn)好像JSON.stringify的方式clone應(yīng)該更快的,畢竟我們的數(shù)據(jù)不存在函數(shù)
// 我這里就先不改了,主要是表達(dá)出思路即可
this.historys.push(_.cloneDeep(payload));
this._currentHistory = this.historys.length - 1;
}
@Mutation
public SET_CURRENT_HISTORY(index: number) {
this._currentHistory = index < 0 ? 0 : index;
}
// 重做
@Action
public RedoHistory(times: number = 1) {
let { state, commit } = this.context;
let historys: HistoryItem[] = state.historys;
let current: number = state._currentHistory;
if (current + times >= historys.length) return;
while (times > 0) {
current++;
let history = historys[current];
if (history) {
commit(history.redo, ...history.redoParams, { root: true });
}
times--;
}
commit("SET_CURRENT_HISTORY", current);
}
// 撤銷(xiāo)
@Action
public UndoHistory(times: number = 1) {
let { state, commit } = this.context;
let historys: HistoryItem[] = state.historys;
let current: number = state._currentHistory;
if (current - times < 0) return;
while (times > 0) {
let history = historys[current];
if (history) {
commit(history.undo, ...history.undoParams, { root: true });
}
times--;
current--;
}
commit("SET_CURRENT_HISTORY", current);
}
}
3. 編寫(xiě)可以撤銷(xiāo)或重做的功能
完成上面兩步后,我們就可以編寫(xiě)各種操作了
編寫(xiě)對(duì)數(shù)據(jù)基礎(chǔ)操作的Mutation
@Mutation
public CREATE_PAGE(payload: { page: PageItem; index: number }) {
this.pages.splice(payload.index, 0, _.cloneDeep(payload.page));
this._currentPage = this.pages.length - 1;
}
@Mutation
public REMOVE_PAGE(id: string) {
let index = this.pages.findIndex((p) => p.id == id);
index > -1 && this.pages.splice(index, 1);
if (this._currentPage == index) {
this._currentPage = this.pages.length > 0 ? 0 : -1;
}
}
將基礎(chǔ)操作按要求封裝成帶保存->記錄->執(zhí)行的Action
// 包裝創(chuàng)建頁(yè)面函數(shù)
@Action
public CreatePage(type: "page" | "dialog") {
let { state, commit } = this.context;
// 記錄保存即將創(chuàng)建的頁(yè)面
let id = _.uniqueId(type) + Date.now();
let pageName = pageType[type];
let page: PageItem = {
id,
name: `${pageName}${state.pages.length + 1}`,
type,
layers: [],
style: { width: 720, height: 1280 },
};
//創(chuàng)建歷史記錄
let history: HistoryItem = {
name: `創(chuàng)建${pageName}`,
timestrap: Date.now(),
redo: "Page/CREATE_PAGE",
redoParams: [{ index: state.pages.length - 1, page }],
undo: "Page/REMOVE_PAGE",
undoParams: [id],
};
// 保存記錄此歷史記錄
commit("Histroy/CREATE_HISTORY", history, { root: true });
commit(history.redo, ...history.redoParams, { root: true });
}
@Action
public RemovePage(id: string) {
// 記錄保存現(xiàn)場(chǎng)狀態(tài)
let index = this.pages.findIndex((p) => p.id == id);
if (index < 0) return;
let page: PageItem = this.context.state.pages[index];
//創(chuàng)建歷史記錄
let history: HistoryItem = {
name: `刪除 ${page.name}`,
timestrap: Date.now(),
redo: "Page/REMOVE_PAGE",
redoParams: [id],
undo: "Page/CREATE_PAGE",
undoParams: [{ page, index }],
};
// 保存記錄此歷史記錄
this.context.commit("Histroy/CREATE_HISTORY", history, { root: true });
this.context.commit(history.redo, ...history.redoParams, { root: true });
}
以上,撤銷(xiāo)與重做的功能就基本完成了
4. 使用
1. 我們現(xiàn)在只需要在使用時(shí)創(chuàng)建或刪除頁(yè)面時(shí)使用封裝的`Action`后
private create(type: "page" | "dialog") {
this.$store.dispatch("Page/CreatePage", type);
}
private remove(id: number) {
this.$store.dispatch("Page/RemovePage", id);
}
2. 配置全局熱鍵
typescript App.vue
private mounted() {
let self = this;
hotkeys("ctrl+z", function (event, handler) {
self.$store.dispatch("History/UndoHistory");
});
hotkeys("ctrl+y", function (event, handler) {
self.$store.dispatch("History/RedoHistory");
});
}
效果

到此這篇關(guān)于vuex實(shí)現(xiàn)歷史記錄的示例代碼的文章就介紹到這了,更多相關(guān)vuex 歷史記錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
uni-app中app和webview的h5通信簡(jiǎn)單步驟
這篇文章主要介紹了如何在nvue頁(yè)面中使用webview組件,并詳細(xì)介紹了如何在h5項(xiàng)目中安裝和配置npmiy_uniwebview插件,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
vue-resource攔截器設(shè)置頭信息的實(shí)例
下面小編就為大家?guī)?lái)一篇vue-resource攔截器設(shè)置頭信息的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
一次前端Vue項(xiàng)目國(guó)際化解決方案的實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于前端Vue項(xiàng)目國(guó)際化解決方案的實(shí)戰(zhàn)記錄,以上只是一部分Vue項(xiàng)目開(kāi)發(fā)中遇到的典型問(wèn)題和解決方案,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
vue3成功創(chuàng)建項(xiàng)目后?run?serve啟動(dòng)項(xiàng)目報(bào)錯(cuò)的解決
這篇文章主要介紹了vue3成功創(chuàng)建項(xiàng)目后?run?serve啟動(dòng)項(xiàng)目報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue組件化(ref,props,?mixin,.插件)詳解
這篇文章主要介紹了Vue組件化(ref,?props,?mixin,?插件)的相關(guān)知識(shí),包括ref屬性,props配置項(xiàng)及mixin混入的方式,本文通過(guò)示例代碼多種方式相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
Vue中emit事件無(wú)法觸發(fā)的問(wèn)題及解決
這篇文章主要介紹了Vue中emit事件無(wú)法觸發(fā)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
Vue數(shù)據(jù)代理的實(shí)現(xiàn)流程逐步講解
通過(guò)一個(gè)對(duì)象代理對(duì)另一個(gè)對(duì)象中的屬性的操作(讀/寫(xiě)),就是數(shù)據(jù)代理。要搞懂Vue數(shù)據(jù)代理這個(gè)概念,那我們就要從Object.defineProperty()入手,Object.defineProperty()是Vue中比較底層的一個(gè)方法,在數(shù)據(jù)劫持,數(shù)據(jù)代理以及計(jì)算屬性等地方都或多或少的用到了本函數(shù)2023-01-01

