Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,37 @@ export type customDirectives = Record<
string,
(node: Element, value: string, modifier: string[]) => void
>;

// Reactivity system

export enum ComputationState {
EXECUTED = 0,
STALE = 1,
PENDING = 2,
}
export type Computation<T = any> = {
compute?: () => T;
state: ComputationState;
sources: Set<Atom | Derived<any, any>>;
isEager?: boolean;
isDerived?: boolean;
value: T; // for effects, this is the cleanup function
childrenEffect?: Computation[]; // only for effects
} & Opts;

export type Opts = {
name?: string;
};
export type Atom<T = any> = {
value: T;
observers: Set<Computation>;
} & Opts;

export interface Derived<Prev, Next = Prev> extends Atom<Next>, Computation<Next> {}

export type OldValue = any;

export type Getter<V> = () => V | null;
export type Setter<T, V> = (this: T, value: V) => void;
export type MakeGetSetReturn<T, V> = readonly [Getter<V>] | readonly [Getter<V>, Setter<T, V>];
export type MakeGetSet<T, V> = (obj: T) => MakeGetSetReturn<T, V>;
60 changes: 25 additions & 35 deletions src/runtime/component_node.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { OwlError } from "../common/owl_error";
import { Atom, Computation, ComputationState } from "../common/types";
import type { App, Env } from "./app";
import { BDom, VNode } from "./blockdom";
import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError } from "./error_handling";
import { OwlError } from "../common/owl_error";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
import { reactive } from "./reactivity";
import { getCurrentComputation, setComputation, withoutReactivity } from "./signals";
import { STATUS } from "./status";
import { batched, Callback } from "./utils";

let currentNode: ComponentNode | null = null;

Expand Down Expand Up @@ -42,7 +43,6 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
// 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
Expand All @@ -54,15 +54,7 @@ const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
* @see reactive
*/
export function useState<T extends object>(state: T): T {
const node = getCurrent();
let render = batchedRenderFunctions.get(node)!;
if (!render) {
render = batched(node.render.bind(node, false));
batchedRenderFunctions.set(node, render);
// manual implementation of onWillDestroy to break cyclic dependency
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
}
return reactive(state, render);
return reactive(state);
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -96,6 +88,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
willPatch: LifecycleHook[] = [];
patched: LifecycleHook[] = [];
willDestroy: LifecycleHook[] = [];
signalComputation: Computation;

constructor(
C: ComponentConstructor<P, E>,
Expand All @@ -109,23 +102,26 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.parent = parent;
this.props = props;
this.parentKey = parentKey;
this.signalComputation = {
value: undefined,
compute: () => this.render(false),
sources: new Set<Atom>(),
state: ComputationState.EXECUTED,
};
const defaultProps = C.defaultProps;
props = Object.assign({}, props);
if (defaultProps) {
applyDefaultProps(props, defaultProps);
}
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}
const previousComputation = getCurrentComputation();
setComputation(this.signalComputation);
this.component = new C(props, env, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
this.component.setup();
setComputation(previousComputation);
currentNode = null;
}

Expand All @@ -142,7 +138,11 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
const component = this.component;
try {
await Promise.all(this.willStart.map((f) => f.call(component)));
let prom: Promise<any[]>;
withoutReactivity(() => {
prom = Promise.all(this.willStart.map((f) => f.call(component)));
});
await prom!;
} catch (e) {
this.app.handleError({ node: this, error: e });
return;
Expand Down Expand Up @@ -257,16 +257,11 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
applyDefaultProps(props, defaultProps);
}

currentNode = this;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}
currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
let prom: Promise<any[]>;
withoutReactivity(() => {
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
});
await prom!;
if (fiber !== this.fiber) {
return;
}
Expand Down Expand Up @@ -383,9 +378,4 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
get name(): string {
return this.component.constructor.name;
}

get subscriptions(): ReturnType<typeof getSubscriptions> {
const render = batchedRenderFunctions.get(this);
return render ? getSubscriptions(render) : [];
}
}
16 changes: 10 additions & 6 deletions src/runtime/fibers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ComponentNode } from "./component_node";
import { fibersInError } from "./error_handling";
import { OwlError } from "../common/owl_error";
import { STATUS } from "./status";
import { runWithComputation } from "./signals";

export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber;
Expand Down Expand Up @@ -133,12 +134,15 @@ export class Fiber {
const node = this.node;
const root = this.root;
if (root) {
try {
(this.bdom as any) = true;
this.bdom = node.renderFn();
} catch (e) {
node.app.handleError({ node, error: e });
}
// todo: should use updateComputation somewhere else.
runWithComputation(node.signalComputation, () => {
try {
(this.bdom as any) = true;
this.bdom = node.renderFn();
} catch (e) {
node.app.handleError({ node, error: e });
}
});
root.setCounter(root.counter - 1);
}
}
Expand Down
22 changes: 16 additions & 6 deletions src/runtime/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Env } from "./app";
import { getCurrent } from "./component_node";
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
import { runWithComputation } from "./signals";
import { inOwnerDocument } from "./utils";

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -86,22 +87,31 @@ export function useEffect<T extends unknown[]>(
effect: Effect<T>,
computeDependencies: () => [...T] = () => [NaN] as never
) {
const context = getCurrent().component.__owl__.signalComputation;

let cleanup: (() => void) | void;
let dependencies: T;

let dependencies: any;
const runEffect = () =>
runWithComputation(context, () => {
cleanup = effect(...dependencies);
});
const computeDependenciesWithContext = () => runWithComputation(context, computeDependencies);

onMounted(() => {
dependencies = computeDependencies();
cleanup = effect(...dependencies);
dependencies = computeDependenciesWithContext();
runEffect();
});

onPatched(() => {
const newDeps = computeDependencies();
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
const newDeps = computeDependenciesWithContext();
const shouldReapply = newDeps.some((val: any, i: number) => val !== dependencies[i]);
if (shouldReapply) {
dependencies = newDeps;
if (cleanup) {
cleanup();
}
cleanup = effect(...dependencies);
runEffect();
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export const blockDom = {
html,
comment,
};

export { App, mount } from "./app";
export { xml } from "./template_set";
export { Component } from "./component";
export type { ComponentConstructor } from "./component";
export { useComponent, useState } from "./component_node";
export { status } from "./status";
export { reactive, markRaw, toRaw } from "./reactivity";
export { effect, withoutReactivity, derived } from "./signals";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
export {
Expand Down
Loading
Loading