This commit is contained in:
Géry Debongnie
2021-11-20 10:45:30 +01:00
parent 89d6b8b7dd
commit b1e79677cb
4 changed files with 52 additions and 43 deletions
+46 -12
View File
@@ -1,6 +1,7 @@
import { useState } from "../reactivity";
import type { App, Env } from "../app/app"; import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom"; import { BDom, VNode } from "../blockdom";
import { clearReactivesForCallback, Reactive, reactive } from "../reactivity";
import { batched, Callback } from "../utils";
import { Component, Props } from "./component"; import { Component, Props } from "./component";
import { fibersInError, handleError } from "./error_handling"; import { fibersInError, handleError } from "./error_handling";
import { import {
@@ -18,6 +19,43 @@ import { applyStyles } from "./style";
let currentNode: ComponentNode | null = null; let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode | null {
return currentNode;
}
// -----------------------------------------------------------------------------
// Integration with reactivity system (useState)
// -----------------------------------------------------------------------------
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> {
if (!batchedRenderFunctions.has(currentNode!)) {
batchedRenderFunctions.set(
currentNode!,
batched(() => currentNode!.render())
);
}
const render = batchedRenderFunctions.get(currentNode!)!;
const reactiveState = reactive(state, render);
// manual implementation of onWillUnmount to break cyclic dependency
currentNode!.willUnmount.unshift( clearReactivesForCallback.bind(null, render))
return reactiveState;
}
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
function arePropsDifferent(props1: Props, props2: Props): boolean { function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) { for (let k in props1) {
if (props1[k] !== props2[k]) { if (props1[k] !== props2[k]) {
@@ -34,6 +72,7 @@ export function component(
parent: any, parent: any,
hasSlots: boolean = false hasSlots: boolean = false
): ComponentNode { ): ComponentNode {
console.warn('asdf')
let node: any = ctx.children[key]; let node: any = ctx.children[key];
let isDynamic = typeof name !== "string"; let isDynamic = typeof name !== "string";
@@ -51,7 +90,9 @@ export function component(
const parentFiber = ctx.fiber!; const parentFiber = ctx.fiber!;
if (node) { if (node) {
console.warn('coucou');
if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) { if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) {
console.warn('coucou3');
node.updateAndRender(props, parentFiber); node.updateAndRender(props, parentFiber);
} }
} else { } else {
@@ -75,14 +116,9 @@ export function component(
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component VNode // Component VNode class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export function getCurrent(): ComponentNode | null {
return currentNode;
}
type LifecycleHook = Function; type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component> export class ComponentNode<T extends typeof Component = typeof Component>
@@ -119,11 +155,9 @@ export class ComponentNode<T extends typeof Component = typeof Component>
applyDefaultProps(props, C); applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env; const env = (parent && parent.childEnv) || app.env;
this.childEnv = env; this.childEnv = env;
if (props) { // if (props) {
props = useState(props); // props = useState(props);
} else { // }
console.trace()
}
this.component = new C(props, env, this) as any; this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this); this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) { if (C.style) {
+2 -1
View File
@@ -34,6 +34,7 @@ import type { AppConfig } from "./app/app";
import { App } from "./app/app"; import { App } from "./app/app";
import { Component } from "./component/component"; import { Component } from "./component/component";
import { getCurrent } from "./component/component_node"; import { getCurrent } from "./component/component_node";
export { useState } from "./component/component_node";
export { App, Component }; export { App, Component };
@@ -55,7 +56,7 @@ export { status } from "./component/status";
export { Portal } from "./portal"; export { Portal } from "./portal";
export { Memo } from "./memo"; export { Memo } from "./memo";
export { css, xml } from "./tags"; export { css, xml } from "./tags";
export { useState, reactive } from "./reactivity"; export { reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks"; export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils"; export { EventBus, whenReady, loadFile, markup } from "./utils";
+3 -29
View File
@@ -1,6 +1,4 @@
import { onWillUnmount } from "./component/lifecycle_hooks"; import { Callback } from "./utils";
import { ComponentNode, getCurrent } from "./component/component_node";
import { batched, Callback } from "./utils";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object) // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target"); const TARGET = Symbol("Target");
@@ -9,7 +7,7 @@ const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol; type ObjectKey = string | number | symbol;
type Target = object; type Target = object;
type Reactive<T extends Target = Target> = T & { export type Reactive<T extends Target = Target> = T & {
[TARGET]: any; [TARGET]: any;
}; };
@@ -81,7 +79,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
* *
* @param callback the callback for which the reactives need to be cleared * @param callback the callback for which the reactives need to be cleared
*/ */
function clearReactivesForCallback(callback: Callback): void { export function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback); const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) { if (!targetsToClear) {
return; return;
@@ -190,27 +188,3 @@ export function reactive<T extends Target>(target: T, callback: Callback): React
return reactivesForTarget.get(callback) as Reactive<T>; return reactivesForTarget.get(callback) as Reactive<T>;
} }
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> {
const node = getCurrent()!;
if (!batchedRenderFunctions.has(node)) {
batchedRenderFunctions.set(
node,
batched(() => node.render())
);
}
const render = batchedRenderFunctions.get(node)!;
const reactiveState = reactive(state, render);
onWillUnmount(() => clearReactivesForCallback(render));
return reactiveState;
}
+1 -1
View File
@@ -419,7 +419,7 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("<div><span></span></div>"); expect(fixture.innerHTML).toBe("<div><span></span></div>");
}); });
test("child can be updated", async () => { test.only("child can be updated", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<t t-esc="props.value"/>`; static template = xml`<t t-esc="props.value"/>`;
} }