mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REM] remove the Memo component
With the new fine grained reactivity system, it was no longer useful.
This commit is contained in:
committed by
Samuel Degueldre
parent
8a472231cf
commit
2a1b99be2d
+5
-29
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
@@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user