[IMP] component: use reactivity to allow shallow renderings

With this commit, component only render child
components if they have different props (shallow
equality). Otherwise, we trust the reactivity
system to make sure that all impacted components
are updated
This commit is contained in:
Géry Debongnie
2022-01-10 14:35:57 +01:00
committed by Samuel Degueldre
parent 592d9a458e
commit 1ae9d514b9
26 changed files with 1177 additions and 189 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ export class Component<Props = any, Env = any> {
setup() {}
render() {
this.__owl__.render();
render(deep: boolean = false) {
this.__owl__.render(deep);
}
}
+47 -14
View File
@@ -1,6 +1,6 @@
import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom";
import { clearReactivesForCallback, NonReactive, Reactive, reactive } from "../reactivity";
import { clearReactivesForCallback, Reactive, reactive, TARGET, NonReactive } from "../reactivity";
import { batched, Callback } from "../utils";
import { Component, ComponentConstructor } from "./component";
import { fibersInError, handleError } from "./error_handling";
@@ -58,14 +58,24 @@ export function useState<T extends object>(state: T): Reactive<T> | NonReactive<
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
type Props = { [key: string]: any };
export function component(
name: string | typeof Component,
props: any,
function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
}
}
return Object.keys(props1).length !== Object.keys(props2).length;
}
export function component<P extends object>(
name: string | ComponentConstructor<P>,
props: P,
key: string,
ctx: ComponentNode,
parent: any
): ComponentNode {
): ComponentNode<P> {
let node: any = ctx.children[key];
let isDynamic = typeof name !== "string";
@@ -83,7 +93,10 @@ export function component(
const parentFiber = ctx.fiber!;
if (node) {
node.updateAndRender(props, parentFiber);
const currentProps = node.component.props[TARGET];
if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
node.updateAndRender(props, parentFiber);
}
} else {
// new component
let C;
@@ -98,8 +111,7 @@ export function component(
node = new ComponentNode(C, props, ctx.app, ctx);
ctx.children[key] = node;
const fiber = makeChildFiber(node, parentFiber);
node.initiateRender(fiber);
node.initiateRender(new Fiber(node, parentFiber));
}
return node;
}
@@ -110,7 +122,7 @@ export function component(
type LifecycleHook = Function;
export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E>> {
export class ComponentNode<P extends object = any, E = any> implements VNode<ComponentNode<P, E>> {
el?: HTMLElement | Text | undefined;
app: App;
fiber: Fiber | null = null;
@@ -141,6 +153,7 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
props = useState(props);
this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
this.component.setup();
@@ -170,21 +183,29 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
}
}
async render() {
async render(deep: boolean = false) {
let current = this.fiber;
if (current && current.root!.locked) {
await Promise.resolve();
// situation may have changed after the microtask tick
current = this.fiber;
}
if (current && !current.bdom && !fibersInError.has(current)) {
return;
}
if (!this.bdom && !current) {
if (current) {
if (!current.bdom && !fibersInError.has(current)) {
if (deep) {
// we want the render from this point on to be with deep=true
current.deep = deep;
}
return;
}
// if current rendering was with deep=true, we want this one to be the same
deep = deep || current.deep;
} else if (!this.bdom) {
return;
}
const fiber = makeRootFiber(this);
fiber.deep = deep;
this.fiber = fiber;
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
@@ -246,6 +267,10 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.fiber = fiber;
const component = this.component;
applyDefaultProps(props, component.constructor as any);
currentNode = this;
props = useState(props);
currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
if (fiber !== this.fiber) {
@@ -310,6 +335,14 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
}
patch() {
if (this.fiber && this.fiber.parent) {
// we only patch here renderings coming from above. renderings initiated
// by the component will be patched independently in the appropriate
// fiber.complete
this._patch();
}
}
_patch() {
const hasChildren = Object.keys(this.children).length > 0;
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
if (hasChildren) {
+4 -4
View File
@@ -16,9 +16,8 @@ export function makeRootFiber(node: ComponentNode): Fiber {
let current = node.fiber;
if (current) {
let root = current.root!;
root.counter -= cancelFibers(current.children);
root.counter = root.counter + 1 - cancelFibers(current.children);
current.children = [];
root.counter++;
current.bdom = null;
if (fibersInError.has(current)) {
fibersInError.delete(current);
@@ -34,7 +33,6 @@ export function makeRootFiber(node: ComponentNode): Fiber {
if (node.patched.length) {
fiber.patched.push(fiber);
}
return fiber;
}
@@ -60,11 +58,13 @@ export class Fiber {
parent: Fiber | null;
children: Fiber[] = [];
appliedToDom = false;
deep: boolean = false;
constructor(node: ComponentNode, parent: Fiber | null) {
this.node = node;
this.parent = parent;
if (parent) {
this.deep = parent.deep;
const root = parent.root!;
root.counter++;
this.root = root;
@@ -107,7 +107,7 @@ export class RootFiber extends Fiber {
current = undefined;
// Step 2: patching the dom
node.patch();
node._patch();
this.locked = false;
// Step 4: calling all mounted lifecycle hooks