I wanted to give MobX a try, in particular from TypeScript.
Here’s my first attempt. I liberally used the documentation example found within createTransformer as my guide.
[javascript]
import "core-js";
import { observable, autorun, createTransformer } from "mobx";
/*
The store that holds our domain: boxes and arrows
*/
class Store implements Storable {
@observable public boxes: Box[];
@observable public arrows: Arrow[];
@observable public selection: any;
constructor(init: Storable = {}) {
this.boxes = init.boxes || [];
this.arrows = init.arrows || [];
this.selection = init.selection;
}
}
interface Storable {
boxes?: any[];
arrows?: Arrow[];
selection?: any;
}
interface Box {
id: string;
caption?: string;
}
interface Arrow {
id: string;
to?: Box;
from?: Box;
}
const serializeState = createTransformer<Store, Store>(store =>
{
return new Store({
boxes: store.boxes.map(serializeBox),
arrows: store.arrows.map(serializeArrow),
selection: store.selection ? store.selection.id : null
});
});
// copy using Object.assign (as this is just a simple JS object anyway)
const serializeBox = createTransformer<Box, Box>(box =>
Object.assign({}, box));
const serializeArrow = createTransformer<Arrow, Arrow>(arrow =>
{
// or can copy manually…
console.log("serializeArrow"); // this is only called 3 times!
return {
id: arrow.id,
to: arrow.to,
from: arrow.from
};
});
const store = new Store();
const states: Storable[] = [];
autorun(() => {
// this could be used to create an undo buffer, or whatever
// probably wouldn’t want infinite growth … :)
states.push(serializeState(store));
});
const b1 = { id: "b1", caption: "Box 1" };
const b2 = { id: "b2", caption: "Box 2" };
const b3 = { id: "b3", caption: "Box 3" };
store.boxes.push(b1);
store.boxes.push(b2);
store.boxes.push(b3);
store.arrows.push({ id: "a1", from: b1, to: b2 });
store.arrows.push({ id: "a2", from: b1, to: b3 });
store.arrows.push({ id: "a3", from: b2, to: b3 });
b1.caption = "Box 1 – Edited";
// Should be 8
console.log(states.length);
b1.caption = "Box 1 – Final";
// Should be 9
console.log(states.length);
[/javascript]
To make that work:
[plain]
npm install –S mobx reflect-metadata
npm install –D @types/core-js
[/plain]
Sweet that MobX includes a TypeScript declarations file. :)
The things of interest here is that MobX assists in maintaining a stack of the object graph’s state, something that could be used for example in an undo buffer or comprehensive log system.
In this example, that’s done by using the MobX autorun functionality. When any of the dependencies of autorun changes, the function executes. In the example above, it makes a clone of the current store, using the createTransformer function, which turns a function into a reactive and memoizing function. Through memoization, it only transforms portions of the objects that have changed, not everything, every time. That doesn’t mean that you won’t want to limit the growth of the states, but you shouldn’t worry that a large complex object structure is being built with every change.
As TypeScript doesn’t support the object spread operator (which is convenient for making a clone of an object), I’ve used Object.assign instead (which may require a polyfill depending on the environment in which you use this code).