mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
wip
This commit is contained in:
@@ -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
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"/>`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user