Sử dụng Zipper để tạo chức năng undo, redo

Giới thiệu

Mình đang thực hiện 1 chức năng cần 1 cấu trúc dữ liệu phục vụ chức năng undo, redo của editor. Khi nghĩ đến cách hoạt động mình nghĩ đến sử dụng 1 cấu trúc dạng Double Linked List. Nhưng do mình khá thích style của Functional programing (FP) nên mình có tìm cách để chuyển về dạng FP. Sau một lúc lục lọi mình tìm ra được cấu trúc gọi là Zipper. Bài viêt này mình sẽ chia sẻ các mình ứng dụng Zipper và chức năng undo redo.

Zipper là gì

Zipper là 1 cấu trúc dữ liệu dùng để xử lý dữ liệu dạng list hoặc tree. Thay vì sử dụng object và tham chiếu Zipper sẽ chỉ sử dụng function. Chi tiết các bạn có thể tham khảo ở vide này


Còn trong bài viêt này mình sẽ chỉ sử dụng dạng đơn giản nhất của zipper :)

Yêu cầu về chức năng

Chúng ta sẽ cần một vài method cho chức năng này:

  1. save (), method giúp chúng ta lưu. Khi chúng ta lưu thì các bước redo sẽ bị xóa hết
  2. undo(), method giúp quay lại phiên bản nào đó chúng ta đã lưu, khi quay lại thì không xóa dữ liệu redo
  3. redo(), method này hoạt động ngược lại undo()

Triển khai

Sau đây là phiên bản triển khai của mình theo Double Linked List


class Snapshot {
  constructor(data) {
    this.payload = data;
    this.next = null;
    this.prev = null;
  }
}

class History {
  constructor() {
    this.current = null;
  }
  save(data) {
    const newDataContainer = new Snapshot(data);
    if (!this.current) {
      this.current = newDataContainer;
    } else {
      if (this.current.next) {
        this.current.next.prev = null;
      }
      this.current.next = newDataContainer;
      newDataContainer.prev = this.current;
      this.current = newDataContainer;
    }
    return this;
  }
  undo() {
    if (this.current && this.current.prev) {
      this.current = this.current.prev;
    }
    return this;
  }
  redo() {
    if (this.current && this.current.next) {
      this.current = this.current.next;
    }
    return this;
  }
}

Bạn có thể run code thử tại đây

Còn đây là phiên bản mình triển khai theo Zipper

const History = (undo = [], current, redo = []) => ({
  save: (data) => History([current, ...undo], data, []),
  undo: () => History(undo.slice(1), undo[0], [current, ...redo]),
  redo: () => History([current, ...undo], redo[0], redo.slice(1)),
  toString: () => `undo: ${JSON.stringify(undo)}, current: ${current}, redo: ${JSON.stringify(redo)}`
});

Bạn có thể run code thử tại đây

Chia sẻ này đơn giản là việc mình muốn thử tìm thêm một giải pháp cho vấn đề, bài này không nhằm so sánh cách nào hơn. Vì vậy nếu bạn có bất kể ý kiến hay chia sẻ gì bạn có thể để lại comment bên dưới.

spider 03-04-2021

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

spider

2 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
1 3
Vì sao viết bài viết này Lấy cảm hứng tử series bài giảng về đại số tuyến tính "Essence of linear algebra" của 3Blue1Brown. Cụ thể là bài giảng về...
spider viết 10 tháng trước
1 3
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
2 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!