В Backbone.js управление состоянием приложения является одной из ключевых задач, особенно при разработке сложных интерфейсов, где пользователю необходимо поддерживать возможность отмены и повторного выполнения действий. Backbone предоставляет гибкую основу для реализации таких механизмов через модели, коллекции и события.
Для реализации Undo/Redo необходимо оперировать состоянием
моделей и их историей изменений. Backbone
модели обладают встроенным событием change, которое
позволяет отслеживать изменения атрибутов:
var Task = Backbone.Model.extend({
defaults: {
title: '',
completed: false
}
});
var task = new Task({ title: 'Пример задачи' });
task.on('change', function(model) {
console.log('Модель изменилась', model.changedAttributes());
});
Событие change фиксирует все модификации, что позволяет
сохранять состояние для последующего возврата.
Для реализации Undo/Redo обычно используют два стека:
Простейшая структура может выглядеть так:
var undoStack = [];
var redoStack = [];
function saveState(model) {
undoStack.push(_.clone(model.attributes));
redoStack = [];
}
В данном примере _.clone из библиотеки Underscore.js
позволяет сохранить независимую копию текущего состояния модели. После
каждой модификации необходимо вызывать saveState.
Undo восстанавливает предыдущее состояние модели:
function undo(model) {
if (undoStack.length === 0) return;
var prevState = undoStack.pop();
redoStack.push(_.clone(model.attributes));
model.set(prevState);
}
Ключевой момент — перед восстановлением состояния нужно сохранить
текущее в redoStack, чтобы появилась возможность выполнить
Redo.
Redo повторно применяет состояние, которое было отменено:
function redo(model) {
if (redoStack.length === 0) return;
var nextState = redoStack.pop();
undoStack.push(_.clone(model.attributes));
model.set(nextState);
}
Важно помнить, что после любого нового действия
redoStack очищается, иначе будут возникать
несогласованности.
Коллекции Backbone (Backbone.Collection) требуют
дополнительной логики, так как операции могут включать
добавление, удаление и
изменение моделей. Для отслеживания изменений коллекции
удобно использовать события:
add — добавление модели;remove — удаление модели;change — изменение модели внутри коллекции.Пример сохранения состояния коллекции:
var Tasks = Backbone.Collection.extend({
model: Task
});
var tasks = new Tasks();
function saveCollectionState(collection) {
undoStack.push(collection.map(model => _.clone(model.attributes)));
redoStack = [];
}
tasks.on('add remove change', function() {
saveCollectionState(tasks);
});
Для Undo/Redo коллекции потребуется восстанавливать набор моделей целиком:
function undoCollection(collection) {
if (undoStack.length === 0) return;
var prevState = undoStack.pop();
redoStack.push(collection.map(model => _.clone(model.attributes)));
collection.reset(prevState.map(attrs => new Task(attrs)));
}
Метод reset позволяет заменить все модели коллекции
одной операцией, сохраняя события Backbone.
_.cloneDeep или аналог, чтобы избежать побочных
эффектов.var AppModel = Backbone.Model.extend({
defaults: {
tasks: new Backbone.Collection()
}
});
var app = new AppModel();
var undoStack = [];
var redoStack = [];
function saveAppState() {
undoStack.push({
tasks: app.get('tasks').map(task => _.clone(task.attributes))
});
redoStack = [];
}
function undoApp() {
if (undoStack.length === 0) return;
var prevState = undoStack.pop();
redoStack.push({
tasks: app.get('tasks').map(task => _.clone(task.attributes))
});
app.get('tasks').reset(prevState.tasks.map(attrs => new Task(attrs)));
}
function redoApp() {
if (redoStack.length === 0) return;
var nextState = redoStack.pop();
undoStack.push({
tasks: app.get('tasks').map(task => _.clone(task.attributes))
});
app.get('tasks').reset(nextState.tasks.map(attrs => new Task(attrs)));
}
Такая структура позволяет одновременно управлять Undo/Redo для отдельных моделей и целых коллекций, обеспечивая консистентность состояния приложения.
Использование стека состояний вместе с событиями Backbone позволяет реализовать полноценную систему Undo/Redo. Ключевые элементы успешной реализации:
change,
add, remove;Эти принципы формируют гибкую основу для сложных интерфейсов с возможностью отмены и повторного выполнения действий.