slide-immutable-js



slide-immutable-js

0 0


slide-immutable-js

Presentation based on this article: http://www.macwright.org/2015/05/18/practical-undo.html slides ==>

On Github veggiemonk / slide-immutable-js

Simple Undo with Immutable.js

source:

http://www.macwright.org/2015/05/18/practical-undo.html Original Article by Tom MacWright

Introduction

The principles are:

  • Data is immutable. It is never mutated in-place.
  • Changes to data are encapsulated into operations that take a previous version and return a new one.
  • History is represented as a list of states, with past on one end, the present on the other, and an index that can back up into 'undo states'.
  • Modifying data causes any future states to be thrown away.

Objects and Arrays are Mutable

var myself = { name: 'Tom' };
var someoneElse = myself;
myself.name = 'Thomas';
// both myself & someoneElse are now equal to { name: 'Thomas' }

// change arrays in place.

var numbers = [3, 2, 1];
var sorted = numbers.sort();
// both numbers & sorted are equal to [1, 2, 3]
// because calling .sort() sorts the array in-place

Immutable.js

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50


// shared structure

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1 === map2); // no change
var map3 = map1.set('b', 50);
assert(map1 !== map3); // change

JS Object <==> Immutable

var myself = Immutable.Map({ name: 'Tom' });
// Instead of changing myself in-place
// like I would with vanilla JavaScript,
// Immutable provides methods that yield new modified objects.

var someoneElse = myself.set('name', 'Thomas');
// If you've dealt with this problem before,
// you might notice that there's another way
// of approaching this problem ==> by cloning objects:

var myself = { name: 'Tom' };
// clone myself, to ensure that changing someoneElse doesn't
// mutate it.
var someoneElse = JSON.parse(JSON.stringify(myself));
myself.name = 'Thomas';

Cloning vs Immutable

var myself = { name: 'Tom' };
var someoneElse = JSON.parse(JSON.stringify(myself));
myself.name = 'Thomas';

Immutable improves upon this hack with a guarantee and an optimization:

  • Immutable's methods like .set() can be more efficient than cloning because they let the new object reference data in the old object: only the changed properties differ. This way you can save memory and performance versus constantly deep-cloning everything.
  • It's nearly impossible to accidentally mutate an Immutable object, and remarkably easy to accidentally forget to clone a normal object. Immutable objects give a strong guarantee that nowhere does anyone mutate data in-place.

Functions Create New Versions

Operations are functions that create new versions

// person is the data, and height is a property
// we want to change. this function creates a new
// version of person *without modifying the old one*
function changeHeight(person, height) {
    return person.set('height', height);
}

History is a List with Index

As simple as that: generally the start of the array is the first state, where there's no ability to go backwards, and the tip of the array is the present.

var historyIndex = 0;
var history = [Immutable.Map({ name: 'Tom' })];
    // history doesn't need to be immutable!

Operations append new versions to history

push a new version on the stack, and run an operation

function operation(fn) {

// eliminate the future
history = history.slice(0, historyIndex + 1);

// create a new version by applying an operation to the head
var newVersion = fn(history[historyIndex]);
    history.push(newVersion);
    historyIndex++;
}
function changeHeight(height) {
    operation(function(data) {
        return data.set('height', height);
    });
}

HistoryIndex: Undo and Redo

var hasUndo = historyIndex !== 0;

var hasRedo = historyIndex !== history.length - 1;

All Together

Please change me!

Operations with name === Actions

Along with an operation, you'll add a little text snippet, like "Drew a circle" or "Changed a color".

function operation(fn, annotation) {
    // eliminate the future
    annotations = annotations.slice(0, historyIndex + 1);
    history = history.slice(0, historyIndex + 1);
    // create a new version by applying an operation to the head
    var newVersion = fn(history[historyIndex]);
    history.push(newVersion);
    annotations.push(annotation);
    historyIndex++;
}
// an operation that adds an annotation
function changeHeight(height) {
  operation(function(data) {
    return data.set('height', height);
  }, 'Changed the height');
}

Thank You

Slides Sources

github.com/veggiemonk

twitter.com/veggiemonk

Simple Undo with Immutable.js source: http://www.macwright.org/2015/05/18/practical-undo.html Original Article by Tom MacWright