CaveDraw源码05:Undo、Redo的实现

Undo、Redo用到了simpleReactive响应式,事件系统,和主类board.js、组件History相互关联,复杂的逻辑,清晰的实现,是整个项目的典范。

History类的实现

History类中有一个数组stack,和一个指向当前历史的指针cur,canUndo、canRedo方法通过当前指针返回true/false。

class History {
    constructor(options) {
        options = options || {};
        this.maxLength = options.maxLength || 30;
        this.callback = options.callback || function callback() {};
        this.stack = [];
        this.cur = -1;
    }

    first() {
        return this.stack[0];
    }

    current() {
        return this.stack[this.cur];
    }

    clear() {
        this.stack = [];
        this.cur = -1;
    }

    save(obj) {
        // undo 过程中,执行保存,则删除后面的,形成新的链
        if (this.cur < this.stack.length - 1) {
            this.stack.splice(this.cur + 1);
        }
        this.stack.push(obj);
        this.cur++;
        if (this.stack.length > this.maxLength) {
            this.stack.shift();
            this.cur--;
        }
        this.callback('save', obj);
    }

    undo() {
        this.cur--;
        this.callback('undo', this.stack[this.cur]);
        return this.stack[this.cur];
    }

    redo() {
        this.cur++;
        this.callback('redo', this.stack[this.cur]);
        return this.stack[this.cur];
    }

    // 最少要有两个历史记录,才能回退
    canUndo = function () {
        return this.cur > 0;
    }

    canRedo = function () {
        return this.cur < this.stack.length - 1;
    }
}

export default History;

UndoBtn的实现

绑定了事件监听board:history,每次点击undo、redo按钮,每次绘图结束,都会触发事件board:history,进而更新UI。通过History类的的canUndo方法更新响应式变量,进而更新UI。

可以说,只要把History类的的canUndo方法逻辑写好,就不会出现错误,可预见性大幅度提高。虽然步骤繁琐了,代码增加了,但是对大脑的负担大大下降。

this.board.ev.bind('board:history', this.updateUIData());
import Controller from './controller';
import {
    simpleReactive,
    effect
} from '../util/simpleReactive';

class UndoBtn extends Controller {
    constructor(board) {
        super(board);
    }

    init() {
        this.name = this.constructor.name;
        this.btn = this.board.template.undoBtn;
        this.ret = simpleReactive({
            active: false,
        });
        this.btn.addEventListener('click', () => {
            if (!this.ret.active) return;
            // undo() 会触发 'board:history' 事件,回调函数在 Board 类中配置了
            this.board._setImage(this.board.history.undo());
        });
        this.reactiveUI();
        this.board.ev.bind('board:history', this.updateUIData());
    }

    onBoardReset() {
        return () => {
            console.log(`触发 board:reset -> target: ${this.name}`);
            this.updateUIData()();
        }
    }

    updateUIData() {
        return () => {
            if (this.board.history.canUndo()) {
                this.ret.active = true;
            } else {
                this.ret.active = false;
            }
        }
    }

    reactiveUI() {
        effect(() => {
            if (this.ret.active) {
                if (!this.btn.classList.contains('active')) {
                    this.btn.classList.add('active');
                }
            } else {
                if (this.btn.classList.contains('active')) {
                    this.btn.classList.remove('active');
                }
            }
        });
    }
}

export default UndoBtn;
Copyright © 2022,枫糖, 版权所有,禁止转载、演绎、商用。
离开前,建议您浏览一下 归档 页面,或许有更多相关的、有趣的内容!
如需博主帮助,请转到 小卖铺 页面,购买手工活服务!

添加评论

code

目录