
将应用程序状态集中在Vuex存储中有许多优点。 优点之一是记录了所有交易。 这使您可以使用方便的功能,例如运行时调试 ,您可以在这些状态之间切换以从执行中分离任务。
在本文中,我将展示如何使用Vuex进一步创建Undo / Redo函数,以进一步回滚/返回,其工作原理类似于调试过程中的调试。 从复杂形式到基于浏览器的游戏,此功能可用于各种情况。
您可以在Github上查看完成的代码 ,并在此Codepen中尝试演示。 如果您想在项目中使用它,我还创建了一个插件作为NPM模块,称为vuex-undo-redo 。
注意:本文最初发布在此处Vue.js 2017/11/13 开发人员博客上 。
插件配置
为了使此功能可重用,我们将其创建为Vue插件。 此功能需要我们向Vue实例添加一些方法和数据,因此我们将插件构造为mixin。
module.exports = { install(Vue) { Vue.mixin({
要在项目中使用它,我们只需导入插件并连接它:
import VuexUndoRedo from './plugin.js'; Vue.use(VuexUndoRedo);
主意
如果用户想要取消,该功能将通过回滚上一个突变来实现,如果用户想重复该突变,则可以重新应用它。 我们该怎么做?
方法一
第一种可能的方法是在每次突变后获取存储库状态的“快照”,并将快照放置在阵列中。 要撤消/重做,我们可以获取正确的快照并将其替换为存储状态。
这种方法的问题在于存储库的状态是一个JavaScript对象。 当您将JavaScript对象放入数组中时,您只需对对象进行引用即可。 像下面这样的幼稚实现将无法工作:
var state = { ... }; var snapshot = [];
快照方法将要求您先进行状态克隆,然后再进行推送。 鉴于Vue的状态通过自动添加get和set函数变为反应性的,因此在克隆时效果不佳。
方法二
另一种可能的方法是注册每个固定的突变。 要取消,我们将商店重置为初始状态,然后再次运行突变; 除了最后一个。 退款类似的概念。
根据Flux的原理,理想情况下,从相同的初始状态重新开始突变应该可以重新创建状态。 由于这是比第一种更清洁的方法,所以让我们继续。
突变登记
Vuex提供了一种用于订阅突变的API方法,我们可以使用它来注册它们。 我们将其设置为created
钩子。 在回调中,我们只是将突变放入一个数组中,以后可以重新运行它。
Vue.mixin({ data() { return { done: [] } }, created() { this.$store.subscribe(mutation => { this.done.push(mutation); } } });
回滚方法
要取消突变,我们清除存储库,然后重新运行除最后一个突变以外的所有突变。 代码的工作方式如下:
- 使用
pop
数组方法删除最后一个突变。 - 使用特殊突变
EMPTY_STATE
清除存储状态(如下所述) - 重复每个剩余的突变,然后在新存储中再次进行修复。 请注意,订阅方法在此过程中仍处于活动状态,即,将再次添加每个突变。 立即使用
pop
删除它。
const EMPTY_STATE = 'emptyState'; Vue.mixin({ data() { ... }, created() { ... }, methods() { undo() { this.done.pop(); this.$store.commit(EMPTY_STATE); this.done.forEach(mutation => { this.$store.commit(`${mutation.type}`, mutation.payload); this.done.pop(); }); } } });
清洁店
每当使用此插件时,开发人员都必须在其存储库中实现一个名为emptyState的变体。 挑战在于将存储恢复到原始状态,以便为从头开始恢复做好准备。
开发人员必须自己执行此操作,因为我们创建的插件无权访问商店,而只能访问Vue实例。 这是一个示例实现:
new Vuex.Store({ state: { myVal: null }, mutations: { emptyState() { this.replaceState({ myval: null }); } } });
回到我们的插件,不应该将emptyState
突变添加到我们的done
列表中,因为我们不想在回滚过程中重新提交它。 通过以下逻辑防止这种情况:
Vue.mixin({ data() { ... }, created() { this.$store.subscribe(mutation => { if (mutation.type !== EMPTY_STATE) { this.done.push(mutation); } }); }, methods() { ... } });
退货方式
让我们创建一个新的data属性,将其undone
为一个数组。 当我们从回滚过程中删除最后一个突变时,我们将其放在以下数组中:
Vue.mixin({ data() { return { done: [], undone: [] } }, methods: { undo() { this.undone.push(this.done.pop()); ... } } });
现在,我们可以创建一个redo
方法,该方法将仅采用最后添加的undone
突变并重新undone
。
methods: { undo() { ... }, redo() { let commit = this.undone.pop(); this.$store.commit(`${commit.type}`, commit.payload); } }
无法退货
如果用户启动取消操作一次或多次,然后进行新的新提交,则undone
的内容将无效。 如果发生这种情况,我们必须清空undone
。
添加提交时,我们可以从订阅回调中发现新的提交。 但是,逻辑很棘手,因为回调没有明显的方法来找出什么是新的提交以及什么是撤消/重做。
最简单的方法是设置newMutation标志。 默认情况下为true,但是rollback和return方法会将其暂时设置为false。 如果将mutation设置为true,则subscribe
回调将清除undone
数组。
module.exports = { install(Vue) { Vue.mixin({ data() { return { done: [], undone: [], newMutation: true }; }, created() { this.$store.subscribe(mutation => { if (mutation.type !== EMPTY_STATE) { this.done.push(mutation); } if (this.newMutation) { this.undone = []; } }); }, methods: { redo() { let commit = this.undone.pop(); this.newMutation = false; this.$store.commit(`${commit.type}`, commit.payload); this.newMutation = true; }, undo() { this.undone.push(this.done.pop()); this.newMutation = false; this.$store.commit(EMPTY_STATE); this.done.forEach(mutation => { this.$store.commit(`${mutation.type}`, mutation.payload); this.done.pop(); }); this.newMutation = true; } } }); }, }
主要功能现已完成! 将插件添加到您自己的项目或我的演示中以对其进行测试。
公开API
在我的演示中,您会注意到,如果当前无法使用取消和返回按钮,则它们将被禁用。 例如,如果尚无提交,则显然无法取消或重做。 使用此插件的开发人员可能想要实现类似的功能。
为此,该插件可以提供两个计算的属性canUndo
和canRedo
作为公共API的一部分。 这很容易实现:
module.exports = { install(Vue) { Vue.mixin({ data() { ... }, created() { ... }, methods: { ... }, computed: {}, computed: { canRedo() { return this.undone.length; }, canUndo() { return this.done.length; } }, }); }, }