Skip to content

Instantly share code, notes, and snippets.

@igrek8
Last active January 8, 2017 14:22
Show Gist options
  • Save igrek8/e4935c450c2ba3b9947d7c3c9b9cc0fe to your computer and use it in GitHub Desktop.
Save igrek8/e4935c450c2ba3b9947d7c3c9b9cc0fe to your computer and use it in GitHub Desktop.
simple immutable state object with reflection
import * as deepMerge from 'deepmerge';
import * as deepFreeze from 'deep-freeze';
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
export class StateObject<T> {
private state: T;
constructor(state: T) {
this.setState(state);
return new Proxy(this, {
get(instance, prop) {
if (prop in instance) {
if (prop in instance.state) {
throw new Error(`Property collision ${prop}`);
}
return instance[prop];
} else {
return instance.state[prop];
}
},
});
}
public setState(transition: (prevState: T) => T, callback?: (newState: T) => void);
public setState(nextState: T, callback?: (newState: T) => void);
public setState(args: T & ((prevState: T) => T), callback?: (newState: T) => void) {
let nextState: T;
if (typeof args === 'function') {
nextState = args(this.state);
}
if (typeof args === 'object') {
nextState = args;
}
this.state = deepMerge.all<T>([this.state, nextState]);
deepFreeze(this.state); // make state immutable
if (callback) {
callback(this.state);
}
}
}
export class StateDependent<T> {
protected readonly stateObject: StateObject<T>;
constructor(stateObject: StateObject<T>) {
this.stateObject = stateObject;
return new Proxy(this, {
get(instance, prop) {
if (prop in instance) {
return instance[prop];
} else {
return instance.stateObject[prop];
}
},
});
}
protected setState(transition: (prevState: T) => T, callback?: (newState: T) => void);
protected setState(nextState: T, callback?: (newState: T) => void);
protected setState(args: T & ((prevState: T) => T), callback?: (newState: T) => void) {
this.stateObject.setState(args, callback);
};
}
interface Header {
readonly headerCode?: number;
}
interface Body {
readonly bodyLength?: number;
}
export class Collector extends StateDependent<Header & Body> implements Header, Body {
// RUNTIME VARIABLES
public readonly headerCode: number;
public readonly bodyLength: number;
// PROXIES
private readonly header: StateDependent<Header>;
private readonly body: StateDependent<Body>;
constructor() {
super(new StateObject<Header & Body>({
bodyLength: 0,
headerCode: 0,
}));
this.header = new StateDependent(this.stateObject);
this.body = new StateDependent(this.stateObject);
}
change() {
this.setState({
headerCode: 100,
bodyLength: 200,
});
}
}
const o = new Collector();
o.change();
console.log(o, o.bodyLength);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment