diff --git a/src/component/component_node.ts b/src/component/component_node.ts index e48554ee..751c42fd 100644 --- a/src/component/component_node.ts +++ b/src/component/component_node.ts @@ -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(); +/** + * 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(state: T): Reactive { + 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 @@ -119,11 +155,9 @@ export class ComponentNode 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) { diff --git a/src/index.ts b/src/index.ts index 3010bb2d..7e5f570a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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"; diff --git a/src/reactivity.ts b/src/reactivity.ts index f11db586..43d80239 100644 --- a/src/reactivity.ts +++ b/src/reactivity.ts @@ -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 & { +export type Reactive = T & { [TARGET]: any; }; @@ -81,7 +79,7 @@ const callbacksToTargets = new WeakMap>(); * * @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(target: T, callback: Callback): React return reactivesForTarget.get(callback) as Reactive; } -const batchedRenderFunctions = new WeakMap(); -/** - * 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(state: T): Reactive { - 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; -} diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts index 7d3784f8..f95234b7 100644 --- a/tests/components/basics.test.ts +++ b/tests/components/basics.test.ts @@ -419,7 +419,7 @@ describe("basics", () => { expect(fixture.innerHTML).toBe("
"); }); - test("child can be updated", async () => { + test.only("child can be updated", async () => { class Child extends Component { static template = xml``; }