[IMP] component: remove updateState method

This commit is contained in:
Géry Debongnie
2019-04-17 10:07:31 +02:00
parent 619e455dd5
commit 94ed6ebe60
3 changed files with 72 additions and 74 deletions
+3 -24
View File
@@ -230,12 +230,12 @@ export class Component<
} }
this._patch(vnode); this._patch(vnode);
target.appendChild(this.el!); target.appendChild(this.el!);
this._observeState();
if (document.body.contains(target)) { if (document.body.contains(target)) {
this._visitSubTree(w => { this._visitSubTree(w => {
if (!w.__owl__.isMounted && this.el!.contains(w.el)) { if (!w.__owl__.isMounted && this.el!.contains(w.el)) {
w.__owl__.isMounted = true; w.__owl__.isMounted = true;
this._observeState();
w.mounted(); w.mounted();
return true; return true;
} }
@@ -326,25 +326,6 @@ export class Component<
return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve(); return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve();
} }
/**
* This is the safest update method for widget: its job is to update the state
* and rerender (if widget is mounted).
*
* Notes:
* - it checks if we do not add extra keys to the state.
* - it is ok to call updateState before the widget is started. In that
* case, it will simply update the state and will not rerender
*/
async updateState(nextState: Partial<State>) {
if (Object.keys(nextState).length === 0) {
return;
}
Object.assign(this.state, nextState);
if (this.__owl__.isStarted) {
await this.render();
}
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Private // Private
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -418,10 +399,10 @@ export class Component<
if (this.__owl__.isMounted) { if (this.__owl__.isMounted) {
return; return;
} }
this._observeState();
if (this.__owl__.parent) { if (this.__owl__.parent) {
if (this.__owl__.parent!.__owl__.isMounted) { if (this.__owl__.parent!.__owl__.isMounted) {
this.__owl__.isMounted = true; this.__owl__.isMounted = true;
this._observeState();
this.mounted(); this.mounted();
const children = this.__owl__.children; const children = this.__owl__.children;
for (let id in children) { for (let id in children) {
@@ -444,9 +425,7 @@ export class Component<
_observeState() { _observeState() {
if (Object.keys(this.state).length) { if (Object.keys(this.state).length) {
this.__owl__.observer.observe(this.state); this.__owl__.observer.observe(this.state);
this.__owl__.observer.notifyCB = () => { this.__owl__.observer.notifyCB = this.render.bind(this);
this.render();
};
} }
} }
} }
+57 -44
View File
@@ -45,14 +45,13 @@ function children(w: Widget): Widget[] {
// Test widgets // Test widgets
class Counter extends Widget { class Counter extends Widget {
// class Counter extends Widget<WEnv, {}, {counter: number}> {
template = "counter"; template = "counter";
state = { state = {
counter: 0 counter: 0
}; };
inc() { inc() {
this.updateState({ counter: this.state.counter + 1 }); this.state.counter++;
} }
} }
@@ -94,7 +93,7 @@ describe("basic widget properties", () => {
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>"); expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0]; const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
await button.click(); await button.click();
await nextMicroTick(); await nextTick();
expect(target.innerHTML).toBe("<div>1<button>Inc</button></div>"); expect(target.innerHTML).toBe("<div>1<button>Inc</button></div>");
}); });
@@ -109,11 +108,12 @@ describe("basic widget properties", () => {
); );
}); });
test("updateState before first render does not trigger a render", async () => { test("changing state before first render does not trigger a render", async () => {
let renderCalls = 0; let renderCalls = 0;
class TestW extends Widget { class TestW extends Widget {
state = { drinks: 1 };
async willStart() { async willStart() {
this.updateState({}); this.state.drinks++;
} }
async _render() { async _render() {
renderCalls++; renderCalls++;
@@ -125,15 +125,6 @@ describe("basic widget properties", () => {
expect(renderCalls).toBe(1); expect(renderCalls).toBe(1);
}); });
test("updateState does not allow adding extra keys", async () => {
const widget = new Widget(env);
try {
await widget.updateState({ extra: 1 });
} catch (e) {
expect(e.message).toMatch("Invalid key:");
}
});
test("keeps a reference to env", async () => { test("keeps a reference to env", async () => {
const widget = new Widget(env); const widget = new Widget(env);
expect(widget.env).toBe(env); expect(widget.env).toBe(env);
@@ -317,7 +308,8 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget(env); const widget = new ParentWidget(env);
await widget.mount(fixture); await widget.mount(fixture);
expect(steps).toEqual(["init", "willstart", "mounted"]); expect(steps).toEqual(["init", "willstart", "mounted"]);
await widget.updateState({ ok: false }); widget.state.ok = false;
await nextTick();
expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]); expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]);
}); });
@@ -329,7 +321,7 @@ describe("lifecycle hooks", () => {
childUnmounted = true; childUnmounted = true;
} }
increment() { increment() {
this.updateState({ n: this.state.n + 1 }); this.state += 1;
} }
} }
@@ -343,10 +335,10 @@ describe("lifecycle hooks", () => {
</div>`; </div>`;
state = { n: 0, flag: true }; state = { n: 0, flag: true };
increment() { increment() {
this.updateState({ n: this.state.n + 1 }); this.state.n += 1;
} }
toggleSubWidget() { toggleSubWidget() {
this.updateState({ flag: !this.state.flag }); this.state.flag = !this.state.flag;
} }
} }
@@ -433,7 +425,7 @@ describe("lifecycle hooks", () => {
expect(fixture.innerHTML).toBe("<span>2</span>"); expect(fixture.innerHTML).toBe("<span>2</span>");
}); });
test("patched hook is called after updateState", async () => { test("patched hook is called after updating State", async () => {
let n = 0; let n = 0;
class TestWidget extends Widget { class TestWidget extends Widget {
@@ -447,10 +439,12 @@ describe("lifecycle hooks", () => {
await widget.mount(fixture); await widget.mount(fixture);
expect(n).toBe(0); expect(n).toBe(0);
await widget.updateState({}); // empty update, should do nothing widget.state.a = 1; // empty update, should do nothing
await nextTick();
expect(n).toBe(0); expect(n).toBe(0);
await widget.updateState({ a: 3 }); widget.state.a = 3;
await nextTick();
expect(n).toBe(1); expect(n).toBe(1);
}); });
@@ -533,10 +527,11 @@ describe("lifecycle hooks", () => {
await widget.mount(fixture); await widget.mount(fixture);
expect(created).toBe(false); expect(created).toBe(false);
expect(mounted).toBe(false); expect(mounted).toBe(false);
await widget.updateState({ flag: true });
widget.state.flag = true;
await nextTick();
expect(mounted).toBe(true); expect(mounted).toBe(true);
expect(created).toBe(true); expect(created).toBe(true);
await widget.updateState({ flag: false });
}); });
test("willPatch/patched hook", async () => { test("willPatch/patched hook", async () => {
@@ -674,7 +669,7 @@ describe("composition", () => {
); );
const button = fixture.getElementsByTagName("button")[0]; const button = fixture.getElementsByTagName("button")[0];
await button.click(); await button.click();
await nextMicroTick(); await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
); );
@@ -709,7 +704,7 @@ describe("composition", () => {
await widget.mount(fixture); await widget.mount(fixture);
const button = fixture.getElementsByTagName("button")[0]; const button = fixture.getElementsByTagName("button")[0];
await button.click(); await button.click();
await nextMicroTick(); await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
); );
@@ -729,13 +724,16 @@ describe("composition", () => {
await widget.mount(fixture); await widget.mount(fixture);
const button = fixture.getElementsByTagName("button")[0]; const button = fixture.getElementsByTagName("button")[0];
await button.click(); await button.click();
await nextMicroTick(); await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
); );
await widget.updateState({ ok: false }); widget.state.ok = false;
await nextTick();
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
await widget.updateState({ ok: true });
widget.state.ok = true;
await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>0<button>Inc</button></div></div>" "<div><div>0<button>Inc</button></div></div>"
); );
@@ -751,16 +749,20 @@ describe("composition", () => {
await widget.mount(fixture); await widget.mount(fixture);
const button = fixture.getElementsByTagName("button")[0]; const button = fixture.getElementsByTagName("button")[0];
await button.click(); await button.click();
await nextMicroTick(); await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
); );
const counter = children(widget)[0]; const counter = children(widget)[0];
expect(counter.__owl__.isMounted).toBe(true); expect(counter.__owl__.isMounted).toBe(true);
await widget.updateState({ ok: false });
widget.state.ok = false;
await nextTick();
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
expect(counter.__owl__.isMounted).toBe(false); expect(counter.__owl__.isMounted).toBe(false);
await widget.updateState({ ok: true });
widget.state.ok = true;
await nextTick();
expect(counter.__owl__.isMounted).toBe(true); expect(counter.__owl__.isMounted).toBe(true);
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div>1<button>Inc</button></div></div>" "<div><div>1<button>Inc</button></div></div>"
@@ -780,9 +782,12 @@ describe("composition", () => {
await widget.mount(fixture); await widget.mount(fixture);
const input = fixture.getElementsByTagName("input")[0]; const input = fixture.getElementsByTagName("input")[0];
input.value = "test"; input.value = "test";
await widget.updateState({ ok: false }); widget.state.ok = false;
await nextTick();
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
await widget.updateState({ ok: true });
widget.state.ok = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><input></div>"); expect(fixture.innerHTML).toBe("<div><input></div>");
const input2 = fixture.getElementsByTagName("input")[0]; const input2 = fixture.getElementsByTagName("input")[0];
expect(input).toBe(input2); expect(input).toBe(input2);
@@ -847,7 +852,8 @@ describe("composition", () => {
} }
const parent = new Parent(env); const parent = new Parent(env);
await parent.mount(fixture); await parent.mount(fixture);
await parent.updateState({ numbers: [1, 3] }); parent.state.numbers = [1, 3];
await nextTick();
expect(normalize(fixture.innerHTML)).toBe( expect(normalize(fixture.innerHTML)).toBe(
normalize(` normalize(`
<div> <div>
@@ -878,7 +884,9 @@ describe("composition", () => {
const parent = new Parent(env); const parent = new Parent(env);
await parent.mount(fixture); await parent.mount(fixture);
const child = children(parent)[0]; const child = children(parent)[0];
await parent.updateState({ flag: true });
parent.state.flag = true;
await nextTick();
expect(children(parent)[0]).toBe(child); expect(children(parent)[0]).toBe(child);
expect(child.__owl__.isDestroyed).toBe(false); expect(child.__owl__.isDestroyed).toBe(false);
expect(normalize(fixture.innerHTML)).toBe( expect(normalize(fixture.innerHTML)).toBe(
@@ -1013,10 +1021,12 @@ describe("other directives with t-widget", () => {
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>"); expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
await widget.updateState({ flag: false }); widget.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
await widget.updateState({ flag: true }); widget.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>"); expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
}); });
@@ -1039,7 +1049,8 @@ describe("other directives with t-widget", () => {
expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>"); expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>");
await widget.updateState({ flag: false }); widget.state.flag = false;
await nextTick();
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>"); expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
}); });
@@ -1062,7 +1073,8 @@ describe("other directives with t-widget", () => {
expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>"); expect(normalize(fixture.innerHTML)).toBe("<div><div>somediv</div></div>");
await widget.updateState({ flag: false }); widget.state.flag = false;
await nextTick();
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>"); expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
}); });
}); });
@@ -1098,7 +1110,8 @@ describe("random stuff/miscellaneous", () => {
const widget = new Parent(env); const widget = new Parent(env);
await widget.mount(fixture); await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>abc</span></div>"); expect(fixture.innerHTML).toBe("<div><span>abc</span></div>");
await widget.updateState({ flag: true }); widget.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>abcdef</span></div>"); expect(fixture.innerHTML).toBe("<div><span>abcdef</span></div>");
}); });
@@ -1225,10 +1238,10 @@ describe("async rendering", () => {
const parent = new Parent(env); const parent = new Parent(env);
await parent.mount(fixture); await parent.mount(fixture);
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>"); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
parent.updateState({ flagA: true }); parent.state.flagA = true;
await nextTick(); await nextTick();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>"); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
parent.updateState({ flagB: true }); parent.state.flagB = true;
await nextTick(); await nextTick();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>"); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("<div></div>");
defB.resolve(); defB.resolve();
@@ -1271,12 +1284,12 @@ describe("async rendering", () => {
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a1</span></div>" "<div><span>a1</span></div>"
); );
parent.updateState({ valA: 2 }); parent.state.valA = 2;
await nextTick(); await nextTick();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a1</span></div>" "<div><span>a1</span></div>"
); );
parent.updateState({ flagB: true }); parent.state.flagB = true;
await nextTick(); await nextTick();
expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe(
"<div><span>a1</span></div>" "<div><span>a1</span></div>"
+12 -6
View File
@@ -406,7 +406,8 @@ describe("connecting a component to store", () => {
await parent.mount(fixture); await parent.mount(fixture);
expect(steps).toEqual(["child:mounted"]); expect(steps).toEqual(["child:mounted"]);
await parent.updateState({ child: false }); parent.state.child = false;
await nextTick();
expect(steps).toEqual(["child:mounted", "child:willUnmount"]); expect(steps).toEqual(["child:mounted", "child:willUnmount"]);
}); });
@@ -479,7 +480,8 @@ describe("connecting a component to store", () => {
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>"); expect(fixture.innerHTML).toBe("<div><span>jupiler</span></div>");
await app.updateState({ beerId: 2 }); app.state.beerId = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>kwak</span></div>"); expect(fixture.innerHTML).toBe("<div><span>kwak</span></div>");
}); });
@@ -562,7 +564,8 @@ describe("connecting a component to store", () => {
"<div><div><span>taster:aaron</span></div></div>" "<div><div><span>taster:aaron</span></div></div>"
); );
await app.updateState({ beerId: 1 }); app.state.beerId = 1;
await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>" "<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
); );
@@ -573,7 +576,8 @@ describe("connecting a component to store", () => {
"<div><div><span>taster:aaron</span><span>selected:jupiler</span><span>consumed:jupiler</span></div></div>" "<div><div><span>taster:aaron</span><span>selected:jupiler</span><span>consumed:jupiler</span></div></div>"
); );
await app.updateState({ beerId: 0 }); app.state.beerId = 0;
await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>" "<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>"
); );
@@ -630,7 +634,8 @@ describe("connecting a component to store", () => {
"<div><div><span>taster:aaron</span></div></div>" "<div><div><span>taster:aaron</span></div></div>"
); );
await app.updateState({ beerId: 1 }); app.state.beerId = 1;
await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>" "<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
); );
@@ -647,7 +652,8 @@ describe("connecting a component to store", () => {
"<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>" "<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>"
); );
await app.updateState({ beerId: 0 }); app.state.beerId = 0;
await nextTick();
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>" "<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>"
); );