diff --git a/src/component/component.ts b/src/component/component.ts index 36bfbe82..ca62a1da 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -1,12 +1,14 @@ import type { ComponentNode } from "./component_node"; +export type Props = { [key: string]: any }; + // ----------------------------------------------------------------------------- // Component Class // ----------------------------------------------------------------------------- export class Component { static template: string = ""; - static props?: any; + static props?: Props; props: Props; env: Env; diff --git a/src/component/component_node.ts b/src/component/component_node.ts index b0c97864..1b84fb8c 100644 --- a/src/component/component_node.ts +++ b/src/component/component_node.ts @@ -1,6 +1,9 @@ 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 } from "./component"; +import { fibersInError, handleError } from "./error_handling"; import { Fiber, makeChildFiber, @@ -9,7 +12,6 @@ import { MountOptions, RootFiber, } from "./fibers"; -import { handleError, fibersInError } from "./error_handling"; import { applyDefaultProps } from "./props_validation"; import { STATUS } from "./status"; @@ -23,6 +25,37 @@ export function useComponent(): Component { return currentNode!.component; } +// ----------------------------------------------------------------------------- +// 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 { + const node = currentNode!; + let render = batchedRenderFunctions.get(node)!; + if (!render) { + render = batched(node.render.bind(node)); + batchedRenderFunctions.set(node, render); + // manual implementation of onWillUnmount to break cyclic dependency + node.willUnmount.unshift(clearReactivesForCallback.bind(null, render)); + } + return reactive(state, render); +} + +// ----------------------------------------------------------------------------- +// component function (used in compiled template code) +// ----------------------------------------------------------------------------- + export function component( name: string | typeof Component, props: any, @@ -69,7 +102,7 @@ export function component( } // ----------------------------------------------------------------------------- -// Component VNode +// Component VNode class // ----------------------------------------------------------------------------- type LifecycleHook = Function; diff --git a/src/index.ts b/src/index.ts index f767bbbe..d4862fd0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,8 @@ export { useComponent } from "./component/component_node"; export { status } from "./component/status"; export { Memo } from "./memo"; export { xml } from "./app/template_set"; -export { useState, reactive } from "./reactivity"; +export { reactive } from "./reactivity"; +export { useState } from "./component/component_node"; export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks"; export { EventBus, whenReady, loadFile, markup } from "./utils"; export { diff --git a/src/reactivity.ts b/src/reactivity.ts index 737c2219..51178ae9 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"); @@ -85,7 +83,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,28 +188,3 @@ export function reactive(target: T, callback: Callback = () => } 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()) - ); - onWillUnmount(() => clearReactivesForCallback(render)); - } - const render = batchedRenderFunctions.get(node)!; - const reactiveState = reactive(state, render); - return reactiveState; -}