diff --git a/CHANGELOG.md b/CHANGELOG.md index cf79c233..314835ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,7 +81,6 @@ All changes are documented here in no particular order. - improved performance - much simpler code - 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)) - breaking: `Context` is removed ([details](#15-context-is-removed)) - 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), 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 -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 -ideas may help: - -- 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 - - - - - - ``` +Migration code: remove the `shouldUpdate` methods, and it should work as well +as before. ### 9. component.el is removed diff --git a/src/index.ts b/src/index.ts index b901704c..ca67b8d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,6 @@ export { App, mount } from "./app/app"; export { Component } from "./component/component"; export { useComponent, useState } from "./component/component_node"; export { status } from "./component/status"; -export { Memo } from "./memo"; export { xml } from "./app/template_set"; export { reactive, markRaw, toRaw } from "./reactivity"; export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks"; diff --git a/src/memo.ts b/src/memo.ts deleted file mode 100644 index b8787bd6..00000000 --- a/src/memo.ts +++ /dev/null @@ -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``; - - 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; -} diff --git a/tests/misc/__snapshots__/memo.test.ts.snap b/tests/misc/__snapshots__/memo.test.ts.snap deleted file mode 100644 index 6501fb70..00000000 --- a/tests/misc/__snapshots__/memo.test.ts.snap +++ /dev/null @@ -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]); - } -}" -`; diff --git a/tests/misc/memo.test.ts b/tests/misc/memo.test.ts deleted file mode 100644 index 2ae3a13a..00000000 --- a/tests/misc/memo.test.ts +++ /dev/null @@ -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``; - } - class Test extends Component { - static template = xml` - - - - `; - - 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` - - - - `; - - 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``; - } - - class Test extends Component { - static template = xml` - - - - - - - - `; - - 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"); - }); -});