[DOC] component: improve internal documentation

This commit is contained in:
Géry Debongnie
2019-08-26 11:37:09 +02:00
parent 670c98916c
commit 9b3a97e732
+71 -13
View File
@@ -38,15 +38,21 @@ export interface Env {
* useful to typecheck and describe the internal keys used by Owl to manage the * useful to typecheck and describe the internal keys used by Owl to manage the
* component tree. * component tree.
*/ */
export interface Meta<T extends Env, Props> { interface Internal<T extends Env, Props> {
// each component has a unique id, useful mostly to handle parent/child
// relationships
readonly id: number; readonly id: number;
vnode: VNode | null; vnode: VNode | null;
isMounted: boolean; isMounted: boolean;
isDestroyed: boolean; isDestroyed: boolean;
// parent and children keys are obviously useful to setup the parent-children
// relationship.
parent: Component<T, any, any> | null; parent: Component<T, any, any> | null;
children: { [key: number]: Component<T, any, any> }; children: { [key: number]: Component<T, any, any> };
// children mapping: from templateID to componentID // children mapping: from templateID to componentID. templateID identifies a
// should it be a map number => Component? // 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 }; cmap: { [key: number]: number };
renderId: number; renderId: number;
@@ -58,10 +64,10 @@ export interface Meta<T extends Env, Props> {
renderPromise: Promise<VNode> | null; renderPromise: Promise<VNode> | null;
boundHandlers: { [key: number]: any }; boundHandlers: { [key: number]: any };
observer?: Observer; observer: Observer | null;
render?: CompiledTemplate; render: CompiledTemplate | null;
mountedHandlers: { [key: number]: Function }; mountedHandlers: { [key: number]: Function };
classObj?: { [key: string]: boolean }; classObj: { [key: string]: boolean } | null;
} }
// If a component does not define explicitely a template // If a component does not define explicitely a template
@@ -76,7 +82,7 @@ const TEMPLATE_MAP: { [key: number]: { [name: string]: string } } = {};
let nextId = 1; let nextId = 1;
export class Component<T extends Env, Props extends {}, State extends {}> { export class Component<T extends Env, Props extends {}, State extends {}> {
readonly __owl__: Meta<Env, Props>; readonly __owl__: Internal<Env, Props>;
template?: string; template?: string;
/** /**
@@ -171,7 +177,10 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
renderPromise: null, renderPromise: null,
renderProps: props || null, renderProps: props || null,
boundHandlers: {}, boundHandlers: {},
mountedHandlers: {} mountedHandlers: {},
observer: null,
render: null,
classObj: null
}; };
} }
@@ -290,6 +299,10 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
} }
} }
/**
* 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() { unmount() {
if (this.__owl__.isMounted) { if (this.__owl__.isMounted) {
this.__callWillUnmount(); this.__callWillUnmount();
@@ -297,6 +310,15 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
} }
} }
/**
* 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<void> { async render(force: boolean = false): Promise<void> {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
if (!__owl__.isMounted) { if (!__owl__.isMounted) {
@@ -384,6 +406,17 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
// Private // 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<any, any, any> | null) { __destroy(parent: Component<any, any, any> | null) {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
const isMounted = __owl__.isMounted; const isMounted = __owl__.isMounted;
@@ -438,6 +471,10 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
} }
} }
/**
* The __updateProps method is called by the t-component directive whenever
* it updates a component (so, when the parent template is rerendered).
*/
async __updateProps( async __updateProps(
nextProps: Props, nextProps: Props,
forceUpdate: boolean = false, forceUpdate: boolean = false,
@@ -457,12 +494,13 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
} }
} }
/**
* Main patching method. We call the virtual dom patch method here to convert
* a virtual dom vnode into some actual dom.
*/
__patch(vnode) { __patch(vnode) {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
const target = __owl__.vnode || document.createElement(vnode.sel!); const target = __owl__.vnode || document.createElement(vnode.sel!);
if (this.__owl__.classObj) {
(<any>vnode).data.class = Object.assign((<any>vnode).data.class || {}, this.__owl__.classObj);
}
__owl__.vnode = patch(target, vnode); __owl__.vnode = patch(target, vnode);
} }
@@ -555,6 +593,14 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
// parent component. With this, we make sure that the parent component will be // parent component. With this, we make sure that the parent component will be
// able to patch itself properly after // able to patch itself properly after
vnode.key = __owl__.id; vnode.key = __owl__.id;
// we applly here the class information described on the component by the
// template (so, something like <MyComponent class="..."/>) to the actual
// root vnode
if (__owl__.classObj) {
vnode.data.class = Object.assign(vnode.data.class || {}, __owl__.classObj);
}
return Promise.all(promises).then(() => vnode); return Promise.all(promises).then(() => vnode);
} }
@@ -584,6 +630,10 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
} }
} }
/**
* Enable the observe feature on the state. We only create an observer if
* there is some state to be observed.
*/
__observeState() { __observeState() {
if (this.state) { if (this.state) {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
@@ -644,13 +694,21 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
// Error handling // 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<any, any, any>) {
let canCatch = false; let canCatch = false;
let qweb = component.env.qweb; let qweb = component.env.qweb;
let root = component; let root = component;
while (component && !(canCatch = component.catchError !== Component.prototype.catchError)) { while (component && !(canCatch = component.catchError !== Component.prototype.catchError)) {
root = component; root = component;
component = component.__owl__.parent; component = component.__owl__.parent!;
} }
console.error(error); console.error(error);
// we trigger error on QWeb so it can be logged/handled // we trigger error on QWeb so it can be logged/handled