基于 Immutable.js 实现撤销重做功能的实例代码

浏览器的功能越来越强大,许多原来由其他客户端提供的功能渐渐转移到了前端,前端应用也越来越复杂。许多前端应用,尤其是一些在线编辑软件,运行时需要不断处理用户的交互,提供了撤消重做功能来保证交互的流畅性。不过为一个应用实现撤销重做功能并不是一件容易的事情。 Redux官方文档中 介绍了如何在 redux 应用中实现撤销重做功能。基于 redux 的撤销功能是一个自顶向下的方案:引入 redux-undo 之后所有的操作都变为了「可撤销的」,然后我们不断修改其配置使得撤销功能变得越来越好用(这也是 redux-undo 有那么多配置项 的原因)。

本文将采用自底向上的思路,以一个简易的在线画图工具为例子,使用TypeScript 、 Immutable.js 实现一个实用的「撤消重做」功能。大致效果如下图所示:

第一步:确定哪些状态需要历史记录,创建自定义的 State 类

并非所有的状态都需要历史记录。许多状态是非常琐碎的,尤其是一些与鼠标或者键盘交互相关的状态,例如在画图工具中拖拽一个图形时我们需要设置一个「正在进行拖拽」的标记,页面会根据该标记显示对应的拖拽提示,显然该拖拽标记不应该出现在历史记录中;而另一些状态无法被撤销或是不需要被撤销,例如网页窗口大小,向后台发送过的请求列表等。

排除那些不需要历史记录的状态,我们将剩下的状态用 Immutable Record 封装起来,并定义 State 类:

// State.ts
import { Record, List, Set } from 'immutable'
const StateRecord = Record({
 items: List<Item>
 transform: d3.ZoomTransform
 selection: number
})
// 用类封装,便于书写 TypeScript,注意这里最好使用Immutable 4.0 以上的版本
export default class State extends StateRecord {}

这里我们的例子是一个简易的在线画图工具,所以上面的 State 类中包含了三个字段,items 用来记录已经绘制的图形,transform 用来记录画板的平移和缩放状态,selection 则表示目前选中的图形的 ID。而画图工具中的其他状态,例如图形绘制预览,自动对齐配置,操作提示文本等,则没有放在 State 类中。

第二步:定义 Action 基类,并为每种不同的操作创建对应的 Action 子类

与 redux-undo 不同的是,我们仍然采用 命令模式 :定义基类 Action,所有对 State 的操作都被封装为一个 Action 的实例;定义若干 Action 的子类,对应于不同类型的操作。

在 TypeScript 中,Action 基类用 Abstract Class 来定义比较方便。

// actions/index.ts
export default abstract class Action {
 abstract next(state: State): State
 abstract prev(state: State): State
 prepare(appHistory: AppHistory): AppHistory { return appHistory }
 getMessage() { return this.constructor.name }
}
      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/284.html