From 9b3a97e7326d279b7938d1bf9a99b73864c55799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 26 Aug 2019 11:37:09 +0200 Subject: [PATCH] [DOC] component: improve internal documentation --- src/component/component.ts | 84 ++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/component/component.ts b/src/component/component.ts index 3641543d..141b2080 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -38,15 +38,21 @@ export interface Env { * useful to typecheck and describe the internal keys used by Owl to manage the * component tree. */ -export interface Meta { +interface Internal { + // each component has a unique id, useful mostly to handle parent/child + // relationships readonly id: number; vnode: VNode | null; isMounted: boolean; isDestroyed: boolean; + + // parent and children keys are obviously useful to setup the parent-children + // relationship. parent: Component | null; children: { [key: number]: Component }; - // children mapping: from templateID to componentID - // should it be a map number => Component? + // children mapping: from templateID to componentID. templateID identifies a + // place in a template. The t-component directive needs it to be able to get + // the component instance back whenever the template is rerendered. cmap: { [key: number]: number }; renderId: number; @@ -58,10 +64,10 @@ export interface Meta { renderPromise: Promise | null; boundHandlers: { [key: number]: any }; - observer?: Observer; - render?: CompiledTemplate; + observer: Observer | null; + render: CompiledTemplate | null; mountedHandlers: { [key: number]: Function }; - classObj?: { [key: string]: boolean }; + classObj: { [key: string]: boolean } | null; } // If a component does not define explicitely a template @@ -76,7 +82,7 @@ const TEMPLATE_MAP: { [key: number]: { [name: string]: string } } = {}; let nextId = 1; export class Component { - readonly __owl__: Meta; + readonly __owl__: Internal; template?: string; /** @@ -171,7 +177,10 @@ export class Component { renderPromise: null, renderProps: props || null, boundHandlers: {}, - mountedHandlers: {} + mountedHandlers: {}, + observer: null, + render: null, + classObj: null }; } @@ -290,6 +299,10 @@ export class Component { } } + /** + * The unmount method is the opposite of the mount method. It is useful + * to call willUnmount calls and remove the component from the DOM. + */ unmount() { if (this.__owl__.isMounted) { this.__callWillUnmount(); @@ -297,6 +310,15 @@ export class Component { } } + /** + * The render method is the main entry point to render a component (once it + * is ready. This method is not initially called when the component is + * rendered the first time). + * + * This method will cause all its sub components to potentially rerender + * themselves. Note that `render` is not called if a component is updated via + * its props. + */ async render(force: boolean = false): Promise { const __owl__ = this.__owl__; if (!__owl__.isMounted) { @@ -384,6 +406,17 @@ export class Component { // Private //-------------------------------------------------------------------------- + /** + * Private helper to perform a full destroy, from the point of view of an Owl + * component. It does not remove the el (this is done only once on the top + * level destroyed component, for performance reasons). + * + * The job of this method is mostly to call willUnmount hooks, and to perform + * all necessary internal cleanup. + * + * Note that it does not call the __callWillUnmount method to avoid visiting + * all children many times. + */ __destroy(parent: Component | null) { const __owl__ = this.__owl__; const isMounted = __owl__.isMounted; @@ -438,6 +471,10 @@ export class Component { } } + /** + * The __updateProps method is called by the t-component directive whenever + * it updates a component (so, when the parent template is rerendered). + */ async __updateProps( nextProps: Props, forceUpdate: boolean = false, @@ -457,12 +494,13 @@ export class Component { } } + /** + * Main patching method. We call the virtual dom patch method here to convert + * a virtual dom vnode into some actual dom. + */ __patch(vnode) { const __owl__ = this.__owl__; const target = __owl__.vnode || document.createElement(vnode.sel!); - if (this.__owl__.classObj) { - (vnode).data.class = Object.assign((vnode).data.class || {}, this.__owl__.classObj); - } __owl__.vnode = patch(target, vnode); } @@ -555,6 +593,14 @@ export class Component { // parent component. With this, we make sure that the parent component will be // able to patch itself properly after vnode.key = __owl__.id; + + // we applly here the class information described on the component by the + // template (so, something like ) to the actual + // root vnode + if (__owl__.classObj) { + vnode.data.class = Object.assign(vnode.data.class || {}, __owl__.classObj); + } + return Promise.all(promises).then(() => vnode); } @@ -584,6 +630,10 @@ export class Component { } } + /** + * Enable the observe feature on the state. We only create an observer if + * there is some state to be observed. + */ __observeState() { if (this.state) { const __owl__ = this.__owl__; @@ -644,13 +694,21 @@ export class Component { // Error handling //------------------------------------------------------------------------------ -function errorHandler(error, component) { +/** + * This is the global error handler for errors occurring in Owl main lifecycle + * methods. Caught errors are triggered on the QWeb instance, and are + * potentially given to some parent component which implements `catchError`. + * + * If there are no such component, we destroy everything. This is better than + * being in a corrupted state. + */ +function errorHandler(error: Error, component: Component) { let canCatch = false; let qweb = component.env.qweb; let root = component; while (component && !(canCatch = component.catchError !== Component.prototype.catchError)) { root = component; - component = component.__owl__.parent; + component = component.__owl__.parent!; } console.error(error); // we trigger error on QWeb so it can be logged/handled