[IMP] store: memoize getters

Closes #95
This commit is contained in:
Aaron Bohy
2019-06-14 22:14:17 +02:00
committed by Géry Debongnie
parent 5ff501f8b8
commit a56939f13a
3 changed files with 159 additions and 1 deletions
+6
View File
@@ -142,6 +142,12 @@ const getters = {
const post = store.getters.getPost(id);
```
Getters take *at most* one argument.
Note that getters are cached if they don't take any argument, or their argument
is a string or a number.
### Connecting a Component
By default, an Owl `Component` is not connected to any store. The `connect`
+14 -1
View File
@@ -47,6 +47,7 @@ export class Store extends EventBus {
env: any;
observer: Observer;
getters: { [name: string]: (payload?) => any };
_gettersCache: { [name: string]: {} };
constructor(config: StoreConfig, options: StoreOption = {}) {
super();
@@ -56,19 +57,31 @@ export class Store extends EventBus {
this.mutations = config.mutations;
this.env = config.env;
this.observer = new Observer();
this.observer.notifyCB = this.trigger.bind(this, "update");
this.observer.notifyCB = () => {
this._gettersCache = {};
this.trigger("update");
};
this.observer.allowMutations = false;
this.observer.observe(this.state);
this.getters = {};
this._gettersCache = {};
if (this.debug) {
this.history.push({ state: this.state });
}
const cTypes = ["undefined", "number", "string"];
for (let entry of Object.entries(config.getters || {})) {
const name: string = entry[0];
const func: (...any) => any = entry[1];
this.getters[name] = payload => {
if (this._commitLevel === 0 && cTypes.indexOf(typeof payload) >= 0) {
this._gettersCache[name] = this._gettersCache[name] || {};
this._gettersCache[name][payload] =
this._gettersCache[name][payload] ||
func({ state: this.state, getters: this.getters }, payload);
return this._gettersCache[name][payload];
}
return func({ state: this.state, getters: this.getters }, payload);
};
}
+139
View File
@@ -182,6 +182,145 @@ describe("basic use", () => {
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
});
test("getters are memoized", async () => {
const state = {
beers: {
1: {
id: 1,
name: "bertinchamps",
tasterID: 1
}
},
tasters: {
1: {
id: 1,
name: "aaron"
}
}
};
let n = 0;
const getters = {
beerTasterName({ state }, beerID) {
n++;
return state.tasters[state.beers[beerID].tasterID].name;
},
bestBeerName({ state }) {
n++;
return state.beers[1].name;
},
};
const store = new Store({ state, mutations: {}, actions: {}, getters });
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
expect(n).toBe(2);
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
expect(n).toBe(2);
});
test("getters taking Array as argument aren't memoized", async () => {
const state = {
beers: {
1: {
id: 1,
name: "bertinchamps",
tasterID: 1
}
},
};
let n = 0;
const getters = {
getBeerNames({ state }, beerIDs) {
n++;
return beerIDs.map(beerID => {
return state.beers[beerID].name;
});
}
};
const store = new Store({ state, mutations: {}, actions: {}, getters });
expect((<any>store.getters).getBeerNames([1])).toEqual(["bertinchamps"]);
expect(n).toBe(1);
expect((<any>store.getters).getBeerNames([1])).toEqual(["bertinchamps"]);
expect(n).toBe(2);
});
test("getters cache is nuked on store changes", async () => {
const state = {
beers: {
1: {
id: 1,
name: "bertinchamps",
tasterID: 1
}
},
tasters: {
1: {
id: 1,
name: "aaron"
},
2: {
id: 2,
name: "gery"
}
}
};
const mutations = {
changeTaster({ state }, {beerID, tasterID}) {
state.beers[beerID].tasterID = tasterID;
}
};
let n = 0;
const getters = {
beerTasterName({ state }, beerID) {
n++;
return state.tasters[state.beers[beerID].tasterID].name;
},
};
const store = new Store({ state, mutations: mutations, actions: {}, getters });
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
expect(n).toBe(1);
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
expect(n).toBe(1);
store.commit('changeTaster', {beerID: 1, tasterID: 2});
await nextTick();
expect((<any>store.getters).beerTasterName(1)).toBe("gery");
expect(n).toBe(2);
});
test("getters cache is disabled during a mutation", async () => {
const state = {
beers: {
1: {
id: 1,
name: "bertinchamps"
},
}
};
const mutations = {
renameBeer({ state, getters }, beerID) {
expect(getters.beerName(beerID)).toBe('bertinchamps');
state.beers[1].name = 'chouffe';
expect(getters.beerName(beerID)).toBe('chouffe');
}
};
let n = 0;
const getters = {
beerName({ state }, beerID) {
n++;
return state.beers[beerID].name;
},
};
const store = new Store({ state, mutations: mutations, actions: {}, getters });
store.commit('renameBeer', 1);
expect((<any>store.getters).beerName(1)).toBe('chouffe');
await nextTick();
expect(n).toBe(3);
});
test("getters given to actions", async () => {
expect.assertions(3);
const state = {