[REM] remove the Memo component

With the new fine grained reactivity system, it was no longer useful.
This commit is contained in:
Géry Debongnie
2022-03-02 10:01:30 +01:00
committed by Samuel Degueldre
parent 8a472231cf
commit 2a1b99be2d
5 changed files with 5 additions and 240 deletions
+5 -29
View File
@@ -81,7 +81,6 @@ All changes are documented here in no particular order.
- improved performance - improved performance
- much simpler code - much simpler code
- new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md)) - new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
- new `Memo` component
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect)) - new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
- breaking: `Context` is removed ([details](#15-context-is-removed)) - breaking: `Context` is removed ([details](#15-context-is-removed))
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty)) - breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
@@ -249,35 +248,12 @@ Rationale: `shouldUpdate` is a dangerous method to use, that may cause a lot of
issues. Vue does not have such a mechanism (see https://github.com/vuejs/vue/issues/4255), issues. Vue does not have such a mechanism (see https://github.com/vuejs/vue/issues/4255),
because the reactivity system in Vue is smart enough to only rerender the minimal because the reactivity system in Vue is smart enough to only rerender the minimal
subset of components that is subscribed to a piece of state. Now, Owl 2 features subset of components that is subscribed to a piece of state. Now, Owl 2 features
a much more powerful reactivity system. a much more powerful reactivity system, so the same rationale applies: in a way,
it's like each Owl 2 component has a `shouldUpdate` method that precisely tracks
every value used by the component.
Migration code: remove the `shouldUpdate` methods. Then, maybe the following Migration code: remove the `shouldUpdate` methods, and it should work as well
ideas may help: as before.
- try to organize the state/architecture to minimize the number of state updates
- take advantage of the finer reactivity system. For example, if we have a list
of items, with a component for each item, we can write this:
```js
class Item extends Component {
setup() {
this.item = useState(this.props.item); // and only use this, not props.item
}
}
```
Doing so will make it that each `Item` component will register itself as an
observer of its own item, and will be the only component being rerendered when
its item object is updated.
- use the `Memo` component to wrap some piece of template. `Memo` memoize its
content, and only update itself if its props are different (shallow comparison):
```xml
<Memo a="state.a" b="state.b">
<t t-esc="state.a"/>
<t t-esc="state.b"/>
<t t-esc="state.c"/>
</Memo>
```
### 9. component.el is removed ### 9. component.el is removed
-1
View File
@@ -42,7 +42,6 @@ export { App, mount } from "./app/app";
export { Component } from "./component/component"; export { Component } from "./component/component";
export { useComponent, useState } from "./component/component_node"; export { useComponent, useState } from "./component/component_node";
export { status } from "./component/status"; export { status } from "./component/status";
export { Memo } from "./memo";
export { xml } from "./app/template_set"; export { xml } from "./app/template_set";
export { reactive, markRaw, toRaw } from "./reactivity"; export { reactive, markRaw, toRaw } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks"; export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
-47
View File
@@ -1,47 +0,0 @@
import { Component } from "./component/component";
import type { ComponentNode } from "./component/component_node";
import { xml } from "./app/template_set";
import { Fiber } from "./component/fibers";
export class Memo extends Component {
static template = xml`<t t-slot="default"/>`;
constructor(props: any, env: any, node: ComponentNode) {
super(props, env, node);
// prevent patching process conditionally
let applyPatch = false;
const patchFn = node.patch;
node.patch = () => {
if (applyPatch) {
patchFn.call(node);
applyPatch = false;
}
};
// check props change, and render/apply patch if it changed
let prevProps = props;
const updateAndRender = node.updateAndRender;
node.updateAndRender = function (props: any, parentFiber: Fiber) {
const shouldUpdate = !shallowEqual(prevProps, props);
if (shouldUpdate) {
prevProps = props;
updateAndRender.call(node, props, parentFiber);
applyPatch = true;
}
return Promise.resolve();
};
}
}
/**
* we assume that each object have the same set of keys
*/
function shallowEqual(p1: any, p2: any): boolean {
for (let k in p1) {
if (k !== "slots" && p1[k] !== p2[k]) {
return false;
}
}
return true;
}
@@ -1,71 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Memo if no prop change, prevent renderings from above 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { markRaw } = helpers;
function slot1(ctx, node, key = \\"\\") {
let b6 = text(ctx['state'].a);
let b7 = text(ctx['state'].b);
let b8 = text(ctx['state'].c);
return multi([b6, b7, b8]);
}
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].a);
let b3 = text(ctx['state'].b);
let b4 = text(ctx['state'].c);
let b9 = component(\`Memo\`, {a: ctx['state'].a, b: ctx['state'].b,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__1\`, node, ctx);
return multi([b2, b3, b4, b9]);
}
}"
`;
exports[`Memo if no props, prevent renderings from above 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { markRaw } = helpers;
function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {value: ctx['state'].value}, key + \`__2\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
let b4 = component(\`Memo\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
return multi([b2, b4]);
}
}"
`;
exports[`Memo if no props, prevent renderings from above 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].value);
}
}"
`;
exports[`Memo if no props, prevent renderings from above (work with simple html) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { markRaw } = helpers;
function slot1(ctx, node, key = \\"\\") {
return text(ctx['state'].value);
}
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value);
let b4 = component(\`Memo\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__1\`, node, ctx);
return multi([b2, b4]);
}
}"
`;
-92
View File
@@ -1,92 +0,0 @@
import { Component, mount, useState, xml } from "../../src";
import { Memo } from "../../src/";
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
beforeEach(() => {
fixture = makeTestFixture();
});
describe("Memo", () => {
test("if no props, prevent renderings from above ", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.value"/>`;
}
class Test extends Component {
static template = xml`
<Child value="state.value"/>
<Memo>
<Child value="state.value"/>
</Memo>`;
static components = { Memo, Child };
state = useState({ value: 1 });
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("11");
component.state.value = 2;
await nextTick();
expect(fixture.innerHTML).toBe("21");
});
test("if no props, prevent renderings from above (work with simple html) ", async () => {
class Test extends Component {
static template = xml`
<t t-esc="state.value"/>
<Memo>
<t t-esc="state.value"/>
</Memo>`;
static components = { Memo };
state = useState({ value: 1 });
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("11");
component.state.value = 2;
await nextTick();
expect(fixture.innerHTML).toBe("21");
});
test("if no prop change, prevent renderings from above ", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.value"/>`;
}
class Test extends Component {
static template = xml`
<t t-esc="state.a"/>
<t t-esc="state.b"/>
<t t-esc="state.c"/>
<Memo a="state.a" b="state.b">
<t t-esc="state.a"/>
<t t-esc="state.b"/>
<t t-esc="state.c"/>
</Memo>`;
static components = { Memo, Child };
state = useState({ a: "a", b: "b", c: "c" });
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("abcabc");
component.state.c = "C";
await nextTick();
expect(fixture.innerHTML).toBe("abCabc");
component.state.a = "A";
await nextTick();
expect(fixture.innerHTML).toBe("AbCAbC");
});
});