Memento menyimpan snapshot state object, lalu mengembalikannya nanti — basis dari undo/redo.
Tiga peran:
- Originator — object yang state-nya disimpan (editor, canvas, form)
- Memento — snapshot state (immutable)
- Caretaker — menyimpan tumpukan memento (history)
Implementasi minimal:
class TextEditor {
constructor() { this.text = ""; }
write(str) { this.text += str; }
// Originator → Memento
save() {
return { text: this.text };
}
// Memento → Originator
restore(memento) {
this.text = memento.text;
}
}
class History {
constructor() { this.stack = []; this.future = []; }
push(memento) {
this.stack.push(memento);
this.future = []; // aksi baru invalidate redo
}
undo() { return this.stack.pop(); }
redo() { return this.future.pop(); }
}
Penggunaan:
const editor = new TextEditor();
const history = new History();
editor.write("Hello");
history.push(editor.save()); // snapshot 1
editor.write(", World");
history.push(editor.save()); // snapshot 2
// Undo
const memento = history.undo();
editor.restore(memento);
console.log(editor.text); // "Hello"
Varian memento:
1. Deep copy snapshot (simple, safe):
save() { return JSON.parse(JSON.stringify(this.state)); }
Cocok untuk state kecil. Mahal untuk state besar.
2. Incremental memento (command pattern): Simpan yang berubah, bukan full snapshot. Undo menerapkan inverse dari command.
const command = { type: "insert", position: 5, text: "lo" };
const inverse = { type: "delete", position: 5, length: 2 };
Ini hybrid Memento + Command Pattern. Lebih efisien untuk editor dengan state besar.
3. Structural sharing (Immer/persistent data structures): State diperlakukan sebagai immutable tree. "Save" hanya pegang reference — versi baru hanya alokasi node yang berubah.
Contoh di Redux DevTools: Setiap dispatched action → snapshot state. User bisa "time travel" ke state lama.
Kapan pakai:
✅ Undo/redo di editor (text, canvas, form builder) ✅ Checkpoint game state ✅ Time-travel debugging ✅ Form "discard changes" → restore ke snapshot awal
❌ State sangat besar + sering berubah → memori boros (pakai incremental) ❌ State punya reference cyclical → deep copy bermasalah
Memory management:
Stack unlimited → memory leak. Batasi:
class History {
constructor(maxSize = 50) {
this.stack = [];
this.maxSize = maxSize;
}
push(memento) {
this.stack.push(memento);
if (this.stack.length > this.maxSize) this.stack.shift();
}
}
Pitfall umum:
- Lupa invalidate redo stack saat action baru → "redo" membawa ke state tidak konsisten
- Shallow copy state yang punya nested objects → undo malah mutate snapshot
- Snapshot setiap keystroke → memory blowup (debounce, atau hanya snapshot saat logical action)