computed properties

pull/954/head
Rich Harris 7 years ago
parent 1cdfb84fec
commit f23c886b6a

@ -8,10 +8,20 @@ import {
} from './shared.js';
function Store(state) {
this._state = state ? assign({}, state) : {};
this._observers = { pre: blankObject(), post: blankObject() };
this._changeHandlers = [];
this._dependents = [];
this._proto = blankObject();
this._changed = blankObject();
this._dependentProps = blankObject();
this._dirty = blankObject();
this._state = Object.create(this._proto);
for (var key in state) {
this._changed[key] = true;
this._state[key] = state[key];
}
}
assign(Store.prototype, {
@ -25,6 +35,17 @@ assign(Store.prototype, {
});
},
_makeDirty: function(prop) {
var dependentProps = this._dependentProps[prop];
if (dependentProps) {
for (var i = 0; i < dependentProps.length; i += 1) {
var dependentProp = dependentProps[i];
this._dirty[dependentProp] = this._changed[dependentProp] = true;
this._makeDirty(dependentProp);
}
}
},
_init: function(props) {
var state = {};
for (let i = 0; i < props.length; i += 1) {
@ -44,6 +65,52 @@ assign(Store.prototype, {
}
},
compute: function(key, deps, fn) {
var store = this;
var value;
store._dirty[key] = true;
for (var i = 0; i < deps.length; i += 1) {
var dep = deps[i];
if (!this._dependentProps[dep]) this._dependentProps[dep] = [];
this._dependentProps[dep].push(key);
}
Object.defineProperty(this._proto, key, {
get: function() {
if (store._dirty[key]) {
var values = deps.map(function(dep) {
if (dep in store._changed) changed = true;
return store._state[dep];
});
var newValue = fn.apply(null, values);
if (differs(newValue, value)) {
value = newValue;
store._changed[key] = true;
var dependentProps = store._dependentProps[key];
if (dependentProps) {
for (var i = 0; i < dependentProps.length; i += 1) {
var prop = dependentProps[i];
store._dirty[prop] = store._changed[prop] = true;
}
}
}
store._dirty[key] = false;
}
return value;
},
set: function() {
throw new Error(`'${key}' is a read-only property`);
}
});
},
onchange: function(callback) {
this._changeHandlers.push(callback);
return {
@ -56,7 +123,7 @@ assign(Store.prototype, {
set: function(newState) {
var oldState = this._state,
changed = {},
changed = this._changed = {},
dirty = false;
for (var key in newState) {
@ -64,7 +131,9 @@ assign(Store.prototype, {
}
if (!dirty) return;
this._state = assign({}, oldState, newState);
this._state = assign(Object.create(this._proto), oldState, newState);
for (var key in changed) this._makeDirty(key);
for (var i = 0; i < this._changeHandlers.length; i += 1) {
this._changeHandlers[i](this._state, changed);

@ -92,4 +92,55 @@ describe('store', () => {
});
});
});
describe('computed', () => {
it('computes a property based on data', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
assert.equal(store.get('bar'), 2);
const values = [];
store.observe('bar', bar => {
values.push(bar);
});
store.set({ foo: 2 });
assert.deepEqual(values, [2, 4]);
});
it('computes a property based on another computed property', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
store.compute('baz', ['bar'], bar => bar * 2);
assert.equal(store.get('baz'), 4);
const values = [];
store.observe('baz', baz => {
values.push(baz);
});
store.set({ foo: 2 });
assert.deepEqual(values, [4, 8]);
});
it('prevents computed properties from being set', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
assert.throws(() => {
store.set({ bar: 'whatever' });
}, /'bar' is a read-only property/);
});
});
});

Loading…
Cancel
Save