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 { BDom, VNode } from "../blockdom";
import { clearReactivesForCallback, Reactive, reactive } from "../reactivity";
import { batched, Callback } from "../utils";
import { Component, Props } from "./component";
import { fibersInError, handleError } from "./error_handling";
import {
@@ -18,6 +19,43 @@ import { applyStyles } from "./style";
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 {
for (let k in props1) {
if (props1[k] !== props2[k]) {
@@ -34,6 +72,7 @@ export function component(
parent: any,
hasSlots: boolean = false
): ComponentNode {
console.warn('asdf')
let node: any = ctx.children[key];
let isDynamic = typeof name !== "string";
@@ -51,7 +90,9 @@ export function component(
const parentFiber = ctx.fiber!;
if (node) {
console.warn('coucou');
if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) {
console.warn('coucou3');
node.updateAndRender(props, parentFiber);
}
} else {
@@ -75,14 +116,9 @@ export function component(
}
// -----------------------------------------------------------------------------
// Component VNode
// Component VNode class
// -----------------------------------------------------------------------------
export function getCurrent(): ComponentNode | null {
return currentNode;
}
type LifecycleHook = Function;
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);
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
if (props) {
props = useState(props);
} else {
console.trace()
}
// if (props) {
// props = useState(props);
// }
this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) {
+2 -1
View File
@@ -34,6 +34,7 @@ import type { AppConfig } from "./app/app";
import { App } from "./app/app";
import { Component } from "./component/component";
import { getCurrent } from "./component/component_node";
export { useState } from "./component/component_node";
export { App, Component };
@@ -55,7 +56,7 @@ export { status } from "./component/status";
export { Portal } from "./portal";
export { Memo } from "./memo";
export { css, xml } from "./tags";
export { useState, reactive } from "./reactivity";
export { reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils";
+3 -29
View File
@@ -1,6 +1,4 @@
import { onWillUnmount } from "./component/lifecycle_hooks";
import { ComponentNode, getCurrent } from "./component/component_node";
import { batched, Callback } from "./utils";
import { Callback } from "./utils";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target");
@@ -9,7 +7,7 @@ const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol;
type Target = object;
type Reactive<T extends Target = Target> = T & {
export type Reactive<T extends Target = Target> = T & {
[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
*/
function clearReactivesForCallback(callback: Callback): void {
export function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) {
return;
@@ -190,27 +188,3 @@ export function reactive<T extends Target>(target: T, callback: Callback): React
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>");
});
test("child can be updated", async () => {
test.only("child can be updated", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.value"/>`;
}