diff --git a/examples/todoapp/templates.xml b/examples/todoapp/templates.xml index a2290c86..d9daafc9 100644 --- a/examples/todoapp/templates.xml +++ b/examples/todoapp/templates.xml @@ -50,7 +50,7 @@ - + diff --git a/src/component.ts b/src/component.ts index 82603c82..4636835a 100644 --- a/src/component.ts +++ b/src/component.ts @@ -319,10 +319,6 @@ export class Component< nextProps: Props, forceUpdate: boolean = false ): Promise { - if (nextProps === this.__owl__.renderProps && !forceUpdate) { - await this.__owl__.renderPromise; - return; - } const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps); return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve(); } diff --git a/src/store.ts b/src/store.ts index 590fc495..4bcb6545 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,4 +1,5 @@ import { EventBus } from "./event_bus"; +import { shallowEqual } from "./utils"; import { Component } from "./component"; //------------------------------------------------------------------------------ @@ -235,17 +236,23 @@ export function makeObserver(): Observer { // Connect function //------------------------------------------------------------------------------ -function setStoreProps(__owl__: any, storeProps: any) { - __owl__.currentStoreProps = storeProps; - __owl__.currentStoreRevs = {}; - __owl__.currentStoreRev = storeProps.__owl__ && storeProps.__owl__.rev; - for (let key in storeProps) { - __owl__.currentStoreRevs[key] = - storeProps[key].__owl__ && storeProps[key].__owl__.rev; +function revNumber(o: T): number { + if (!("__owl__" in o)) { + return 0; } + return (o).__owl__.rev; } -export function connect(mapStateToProps) { +function deepRevNumber(o: T): number { + if (!("__owl__" in o)) { + return 0; + } + return (o).__owl__.deepRev; +} + +export function connect(mapStateToProps, options: any = {}) { + let hashFunction = options.hashFunction || null; + return function(Comp) { return class extends Comp { constructor(parent, props?: any) { @@ -254,32 +261,60 @@ export function connect(mapStateToProps) { const storeProps = mapStateToProps(env.store.state, ownProps); const mergedProps = Object.assign({}, props || {}, storeProps); super(parent, mergedProps); - setStoreProps(this.__owl__, storeProps); this.__owl__.ownProps = ownProps; + this.__owl__.currentStoreProps = storeProps; + if (!hashFunction) { + if ("__owl__" in storeProps) { + hashFunction = s => deepRevNumber(s.storeProps); + } else { + let areKeyObservable = false; + for (let key in storeProps) { + areKeyObservable = + areKeyObservable || "__owl__" in storeProps[key]; + } + if (areKeyObservable) { + hashFunction = function({ storeProps }) { + return Object.values(storeProps).reduce( + (sum: number, val: any) => sum + deepRevNumber(val), + 0 + ); + }; + } + } + } + if (hashFunction) { + this.__owl__.storeHash = hashFunction({ + state: env.store.state, + storeProps: storeProps, + revNumber, + deepRevNumber + }); + } } mounted() { this.env.store.on("update", this, () => { const ownProps = this.__owl__.ownProps; const storeProps = mapStateToProps(this.env.store.state, ownProps); let didChange = false; - if ( - this.__owl__.currentStoreRev && - this.__owl__.currentStoreRev !== storeProps.__owl__.rev - ) { - setStoreProps(this.__owl__, storeProps); - didChange = true; - } else { - const revs = this.__owl__.currentStoreRevs; - for (let key in storeProps) { - const val = storeProps[key]; - if (val.__owl__ && val.__owl__.rev !== revs[key]) { - didChange = true; - revs[key] = val.__owl__ && val.__owl__.rev; - this.__owl__.currentStoreProps[key] = val; - } + if (hashFunction) { + const storeHash = hashFunction({ + state: this.env.store.state, + storeProps: storeProps, + revNumber, + deepRevNumber + }); + if (storeHash !== this.__owl__.storeHash) { + didChange = true; + this.__owl__.storeHash = storeHash; } + } else { + didChange = !shallowEqual( + storeProps, + this.__owl__.currentStoreProps + ); } if (didChange) { + this.__owl__.currentStoreProps = storeProps; this.updateProps(ownProps, false); } }); diff --git a/src/utils.ts b/src/utils.ts index 89ea0d55..4f686290 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -113,6 +113,19 @@ export function findInTree>( return null; } +export function shallowEqual(objA, objB) { + if (objA === objB) { + return true; + } + const keysA = Object.keys(objA); + for (let key of keysA) { + if (!(key in objB) || objA[key] !== objB[key]) { + return false; + } + } + return true; +} + export function patch(C: any, patchName: string, patch: any) { const proto = C.prototype; if (!proto.__patches) { diff --git a/tests/utils.test.ts b/tests/utils.test.ts index b15ba167..980be636 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -5,6 +5,7 @@ import { memoize, debounce, findInTree, + shallowEqual, patch, unpatch } from "../src/utils"; @@ -94,6 +95,17 @@ describe("findInTree", () => { }); }); +describe("shallowEqual", () => { + test("simple comparisons", () => { + const obj1 = {}; + expect(shallowEqual(obj1, obj1)).toBe(true); + expect(shallowEqual({}, {})).toBe(true); + expect(shallowEqual({ a: 1 }, {})).toBe(false); + expect(shallowEqual({ a: 1 }, { a: 1 })).toBe(true); + expect(shallowEqual({ a: 1 }, ["a"])).toBe(false); + }); +}); + describe("patch/unpatch", () => { test("can monkey patch a class", () => { class Test {