mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
451 lines
15 KiB
Markdown
451 lines
15 KiB
Markdown
# 🦉 Owl Component 🦉
|
|
|
|
## Content
|
|
|
|
- [Overview](#overview)
|
|
- [Properties and methods](#properties-and-methods)
|
|
- [Static Properties](#static-properties)
|
|
- [Lifecycle](#lifecycle)
|
|
- [`setup`](#setup)
|
|
- [`willStart`](#willstart)
|
|
- [`willRender`](#willrender)
|
|
- [`rendered`](#rendered)
|
|
- [`mounted`](#mounted)
|
|
- [`willUpdateProps`](#willupdateprops)
|
|
- [`willPatch`](#willpatch)
|
|
- [`patched`](#patched)
|
|
- [`willUnmount`](#willunmount)
|
|
- [`willDestroy`](#willdestroy)
|
|
- [`onError`](#onerror)
|
|
- [Sub components](#sub-components)
|
|
- [Dynamic Sub components](#dynamic-sub-components)
|
|
- [`status` helper](#status-helper)
|
|
|
|
## Overview
|
|
|
|
An Owl component is a small class which represents some part of the user interface.
|
|
It is part of a component tree, and has an [environment](environment.md) (`env`),
|
|
which is propagated from a parent to its children.
|
|
|
|
OWL components are defined by subclassing the `Component` class. For example,
|
|
here is how a `Counter` component could be implemented:
|
|
|
|
```javascript
|
|
const { Component, xml, useState } = owl;
|
|
|
|
class Counter extends Component {
|
|
static template = xml`
|
|
<button t-on-click="increment">
|
|
Click Me! [<t t-esc="state.value"/>]
|
|
</button>`;
|
|
|
|
state = useState({ value: 0 });
|
|
|
|
increment() {
|
|
this.state.value++;
|
|
}
|
|
}
|
|
```
|
|
|
|
In this example, we use the `xml` helper to define inline templates, and the
|
|
`useState` hook, which returns a reactive version of its argument (see the page
|
|
on reactivity).
|
|
|
|
## Properties and methods
|
|
|
|
The `Component` class has a very small API.
|
|
|
|
- **`env (object)`**: the component [environment](environment.md)
|
|
|
|
- **`props (object)`**: this is an object containing all the [props](props.md) given by
|
|
the parent to a child component
|
|
|
|
Note that `props` are owned by the parent, not by the component.
|
|
As such, it should not ever be modified by the component (otherwise you risk
|
|
unintended effects, since the parent may not be aware of the change)!!
|
|
|
|
The `props` can be modified dynamically by the parent. In that case, the
|
|
component will go through the following lifecycle methods: `willUpdateProps`,
|
|
`willPatch` and `patched`.
|
|
|
|
* **`render()`**: calling this method directly will cause a rerender. Note
|
|
that with the reactivity system, this should be rare to have to do it manually.
|
|
Also, the rendering operation is asynchronous, so the DOM will only be updated
|
|
slightly later (at the next animation frame, if no component delays the
|
|
rendering)
|
|
|
|
## Static Properties
|
|
|
|
- **`template (string)`**: this is the name of the template that
|
|
will render the component. Note that there is a helper `xml` to
|
|
make it easy to define an inline template.
|
|
|
|
* **`components (object, optional)`**: if given, this is an object that contains
|
|
the classes of any sub components needed by the template.
|
|
|
|
```js
|
|
class ParentComponent extends owl.Component {
|
|
static components = { SubComponent };
|
|
}
|
|
```
|
|
|
|
* **`props (object, optional)`**: if given, this is an object that describes the
|
|
type and shape of the (actual) props given to the component. If Owl mode is
|
|
`dev`, this will be used to validate the props each time the component is
|
|
created/updated. See [Props Validation](props.md#props-validation) for more information.
|
|
|
|
```js
|
|
class Counter extends owl.Component {
|
|
static props = {
|
|
initialValue: Number,
|
|
optional: true,
|
|
};
|
|
}
|
|
```
|
|
|
|
- **`defaultProps (object, optional)`**: if given, this object define default
|
|
values for (top-level) props. Whenever `props` are given to the object, they
|
|
will be altered to add default value (if missing). Note that it does not
|
|
change the initial object, a new object will be created instead. See
|
|
[default props](props.md#default-props) for more information
|
|
|
|
```js
|
|
class Counter extends owl.Component {
|
|
static defaultProps = {
|
|
initialValue: 0,
|
|
};
|
|
}
|
|
```
|
|
|
|
## Lifecycle
|
|
|
|
A solid and robust component system needs a complete lifecycle system to help
|
|
developers write components. Here is a complete description of the lifecycle of
|
|
a Owl component:
|
|
|
|
| Method | Hook | Description |
|
|
| --------------------------------------- | ------------------- | ---------------------------------------------------------------------- |
|
|
| **[setup](#setup)** | none | setup |
|
|
| **[willStart](#willstart)** | `onWillStart` | async, before first rendering |
|
|
| **[willRender](#willrender)** | `onWillRender` | just before component is rendered |
|
|
| **[rendered](#rendered)** | `onRendered` | just after component is rendered |
|
|
| **[mounted](#mounted)** | `onMounted` | just after component is rendered and added to the DOM |
|
|
| **[willUpdateProps](#willupdateprops)** | `onWillUpdateProps` | async, before props update |
|
|
| **[willPatch](#willpatch)** | `onWillPatch` | just before the DOM is patched |
|
|
| **[patched](#patched)** | `onPatched` | just after the DOM is patched |
|
|
| **[willUnmount](#willunmount)** | `onWillUnmount` | just before removing component from DOM |
|
|
| **[willDestroy](#willdestroy)** | `onWillDestroy` | just before component is destroyed |
|
|
| **[error](#onerror)** | `onError` | catch and handle errors (see [error handling page](error_handling.md)) |
|
|
|
|
### `setup`
|
|
|
|
_setup_ is run just after the component is constructed. It is a lifecycle method,
|
|
very similar to the _constructor_, except that it does not receive any argument.
|
|
|
|
It is the proper place to call hook functions. Note that one of the main reason to
|
|
have the `setup` hook in the component lifecycle is to make it possible to
|
|
monkey patch it. It is a common need in the Odoo ecosystem.
|
|
|
|
```javascript
|
|
setup() {
|
|
useSetupAutofocus();
|
|
}
|
|
```
|
|
|
|
### `willStart`
|
|
|
|
`willStart` is an asynchronous hook that can be implemented to
|
|
perform some (most of the time asynchronous) action before the initial rendering of a component.
|
|
|
|
It will be called exactly once before the initial rendering. It is useful
|
|
in some cases, for example, to load external assets (such as a JS library)
|
|
before the component is rendered. Another use case is to load data from a server.
|
|
|
|
The `onWillStart` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onWillStart(async () => {
|
|
this.data = await this.loadData()
|
|
});
|
|
}
|
|
```
|
|
|
|
At this point, the component is not yet rendered. Note that slow `willStart`
|
|
code will slow down the rendering of the user interface. Therefore, some care
|
|
should be made to make this method as fast as possible.
|
|
|
|
Note that if there are more than one `onWillStart` registered callback, then they
|
|
will all be run in parallel.
|
|
|
|
### `willRender`
|
|
|
|
It is uncommon but it may happen that one need to execute code just before a
|
|
component is rendered (more precisely, when its compiled template function is executed).
|
|
To do that, one can use the `onWillRender` hook:
|
|
|
|
```javascript
|
|
setup() {
|
|
onWillRender(() => {
|
|
// do something
|
|
});
|
|
}
|
|
```
|
|
|
|
`willRender` hooks are called just before rendering templates, parent first,
|
|
then children.
|
|
|
|
### `rendered`
|
|
|
|
It is uncommon but it may happen that one need to execute code just after a
|
|
component is rendered (more precisely, when its compiled template function is executed).
|
|
To do that, one can use the `onRendered` hook:
|
|
|
|
```javascript
|
|
setup() {
|
|
onRendered(() => {
|
|
// do something
|
|
});
|
|
}
|
|
```
|
|
|
|
`rendered` hooks are called just after rendering templates, parent first,
|
|
then children. Note that at this moment, the actual DOM may not exist yet (if
|
|
it is the first rendering), or is not updated yet. This will be dom in the next
|
|
animation frame as soon as all the components are ready.
|
|
|
|
### `mounted`
|
|
|
|
The `mounted` hook is called each time a component is attached to the
|
|
DOM, after the initial rendering. At this point, the component is considered
|
|
_active_. This is a good place to add some listeners, or to interact with the
|
|
DOM, if the component needs to perform some measure for example.
|
|
|
|
It is the opposite of `willUnmount`. If a component has been mounted, it will
|
|
always be unmounted at some point in the future.
|
|
|
|
The mounted method will be called recursively on each of its children. First,
|
|
children, then parents.
|
|
|
|
It is allowed (but not encouraged) to modify the state in the `mounted` hook.
|
|
Doing so will cause a rerender, which will not be perceptible by the user, but
|
|
will slightly slow down the component.
|
|
|
|
The `onMounted` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onMounted(() => {
|
|
// do something here
|
|
});
|
|
}
|
|
```
|
|
|
|
### `willUpdateProps`
|
|
|
|
The `willUpdateProps` is an asynchronous hook, called just before new props
|
|
are set. This is useful if the component needs to perform an asynchronous task,
|
|
depending on the props (for example, assuming that the props are
|
|
some record Id, fetching the record data).
|
|
|
|
The `onWillUpdateProps` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onWillUpdateProps(nextProps => {
|
|
return this.loadData({id: nextProps.id});
|
|
});
|
|
}
|
|
```
|
|
|
|
Notice that it receives the next props for the component.
|
|
|
|
This hook is not called during the first render (but `willStart` is called
|
|
and performs a similar job). Also, as most of the hooks, it is called in the
|
|
usual order: parents first, then children.
|
|
|
|
### `willPatch`
|
|
|
|
The willPatch hook is called just before the DOM patching process starts.
|
|
It is not called on the initial render. This is useful to read
|
|
information from the DOM. For example, the current position of the
|
|
scrollbar.
|
|
|
|
Note that modifying the state is not allowed here. This method is called just
|
|
before an actual DOM patch, and is only intended to be used to save some local
|
|
DOM state. Also, it will not be called if the component is not in the DOM.
|
|
|
|
The `onWillPatch` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onWillPatch(() => {
|
|
this.scrollState = this.getScrollSTate();
|
|
});
|
|
}
|
|
```
|
|
|
|
The `willPatch` is called in the usual parent->children order.
|
|
|
|
### `patched`
|
|
|
|
This hook is called whenever a component did actually update its DOM (most
|
|
likely via a change in its state/props or environment).
|
|
|
|
This method is not called on the initial render. It is useful to interact
|
|
with the DOM (for example, through an external library) whenever the
|
|
component was patched. Note that this hook will not be called if the component is
|
|
not in the DOM.
|
|
|
|
The `onPatched` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onPatched(() => {
|
|
this.scrollState = this.getScrollSTate();
|
|
});
|
|
}
|
|
```
|
|
|
|
Updating the component state in this hook is possible, but not encouraged.
|
|
One needs to be careful, because updates here will create an additional rendering, which in
|
|
turn will cause other calls to the `patched` method. So, we need to be particularly
|
|
careful at avoiding endless cycles.
|
|
|
|
Like `mounted`, the `patched` hook is called in the order: children first, then
|
|
parent.
|
|
|
|
### `willUnmount`
|
|
|
|
`willUnmount` is a hook that is called each time just before a component is
|
|
unmounted from the DOM. This is a good place to remove listeners, for example.
|
|
|
|
The `onWillUnmount` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onMounted(() => {
|
|
// add some listener
|
|
});
|
|
onWillUnmount(() => {
|
|
// remove listener
|
|
});
|
|
}
|
|
```
|
|
|
|
This is the opposite method of `mounted`. Note that if a component is destroyed
|
|
before being mounted, the `willUnmount` method may not be called.
|
|
|
|
Parent `willUnmount` hooks will be called before children.
|
|
|
|
### `willDestroy`
|
|
|
|
Sometimes, components need to do some action in the `setup` and clean it up when
|
|
they are inactive. However, the `willUnmount` hook is not appropriate for the
|
|
cleaning operation, since the component may be destroyed before it has even been
|
|
mounted. The `willDestroy` hook is useful in that situation, since it is always
|
|
called.
|
|
|
|
The `onWillUnmount` hook is used to register a function that will be executed at
|
|
this moment:
|
|
|
|
```javascript
|
|
setup() {
|
|
onWillDestroy(() => {
|
|
// do some cleanup
|
|
});
|
|
}
|
|
```
|
|
|
|
The `willDestroy` hooks are first called on children, then on parents.
|
|
|
|
### `onError`
|
|
|
|
Sadly, it may happen that components crashes at runtime. This is an unfortunate
|
|
reality, and this is why Owl needs to provide a way to handle these errors.
|
|
|
|
The `onError` hook is useful when we need to intercept and properly react
|
|
to errors that occur in some sub components. See the page on
|
|
[error handling](error_handling.md) for more detail.
|
|
|
|
```javascript
|
|
setup() {
|
|
onError(() => {
|
|
// do something
|
|
});
|
|
}
|
|
```
|
|
|
|
## Sub components
|
|
|
|
It is convenient to define a component using other (sub) components. This is
|
|
called composition, and is very powerful in practice. To do that in Owl, one
|
|
can just use a tag starting with a capital letter in its template, and register
|
|
the sub component class in its static `components` object:
|
|
|
|
```js
|
|
class Child extends Component {
|
|
static template = xml`<div>child component <t t-esc="props.value"/></div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<Child value="1"/>
|
|
<Child value="2"/>
|
|
</div>`;
|
|
|
|
static components = { Child };
|
|
}
|
|
```
|
|
|
|
This example also shows how one can pass information from the parent component
|
|
to the child component, as props. See the [props section](props.md)
|
|
for more information.
|
|
|
|
## Dynamic sub components
|
|
|
|
It is not common, but sometimes we need a dynamic component name. In this case,
|
|
the `t-component` directive can also be used to accept dynamic values. This should
|
|
be an expression that evaluates to a component class. For example:
|
|
|
|
```js
|
|
class A extends Component {
|
|
static template = xml`<div>child a</div>`;
|
|
}
|
|
class B extends Component {
|
|
static template = xml`<span>child b</span>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<t t-component="myComponent"/>`;
|
|
|
|
state = useState({ child: "a" });
|
|
|
|
get myComponent() {
|
|
return this.state.child === "a" ? A : B;
|
|
}
|
|
}
|
|
```
|
|
|
|
## `status` helper
|
|
|
|
It is sometimes convenient to have a way to find out in which state a component
|
|
is currently. To do that, one can use the `status` helper:
|
|
|
|
```js
|
|
const { status } = owl;
|
|
// assume component is an instance of a Component
|
|
|
|
console.log(status(component));
|
|
// logs either:
|
|
// - 'new', if the component is new and has not been mounted yet
|
|
// - 'mounted', if the component is currently mounted
|
|
// - 'destroyed' if the component is currently destroyed
|
|
```
|