[IMP] component: add setup lifecycle hook

This commit is contained in:
Géry Debongnie
2020-10-30 09:33:49 +01:00
committed by aab-odoo
parent 398f9f4e53
commit 19a47a7001
5 changed files with 67 additions and 52 deletions
+19 -1
View File
@@ -11,6 +11,7 @@
- [Methods](#methods)
- [Lifecycle](#lifecycle)
- [`constructor(parent, props)`](#constructorparent-props)
- [`setup()`](#setup)
- [`willStart()`](#willstart)
- [`mounted()`](#mounted)
- [`willUpdateProps(nextProps)`](#willupdatepropsnextprops)
@@ -324,7 +325,7 @@ a owl component:
| Method | Description |
| ------------------------------------------------ | ----------------------------------------------------------- |
| **[constructor](#constructorparent-props)** | constructor |
| **[setup](#setup)** | setup |
| **[willStart](#willstart)** | async, before first rendering |
| **[mounted](#mounted)** | just after component is rendered and added to the DOM |
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
@@ -369,6 +370,23 @@ class ClickCounter extends owl.Component {
}
```
Hook functions can be called in the constructor.
#### `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 a valid method 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
+8 -1
View File
@@ -131,7 +131,7 @@ class SomeComponent extends Component {
### One rule
There is only one rule: every hook for a component has to be called in the
constructor (or in class fields):
constructor, in the _setup_ method, or in class fields:
```js
// ok
@@ -147,6 +147,13 @@ class SomeComponent extends Component {
}
}
// also ok
class SomeComponent extends Component {
setup() {
this.state = useState({ value: 0 });
}
}
// not ok: this is executed after the constructor is called
class SomeComponent extends Component {
async willStart() {
+12
View File
@@ -209,8 +209,20 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (constr.style) {
this.__applyStyles(constr);
}
this.setup();
}
/**
* setup is run just after the component is constructed. This is the standard
* location where the component can setup its hooks. It has some advantages
* over the constructor:
* - it can be patched (useful in odoo ecosystem)
* - it does not need to propagate the arguments to the super call
*
* Note: this method should not be called manually.
*/
setup() {}
/**
* willStart is an asynchronous hook that can be implemented to perform some
* action before the initial rendering of a component.
+6 -9
View File
@@ -661,9 +661,8 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent) {
super(parent);
steps.push("init");
setup() {
steps.push("setup");
}
async willStart() {
steps.push("willstart");
@@ -687,10 +686,10 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget();
await widget.mount(fixture);
expect(steps).toEqual(["init", "willstart", "mounted"]);
expect(steps).toEqual(["setup", "willstart", "mounted"]);
widget.state.ok = false;
await nextTick();
expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]);
expect(steps).toEqual(["setup", "willstart", "mounted", "willunmount"]);
});
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
@@ -738,8 +737,7 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent) {
super(parent);
setup() {
steps.push("c init");
}
async willStart() {
@@ -755,8 +753,7 @@ describe("lifecycle hooks", () => {
class ParentWidget extends Component {
static template = xml`<div><t t-component="child"/></div>`;
static components = { child: ChildWidget };
constructor(parent?) {
super(parent);
setup() {
steps.push("p init");
}
async willStart() {
+22 -41
View File
@@ -2,8 +2,7 @@ const COMPONENTS = `// In this example, we show how components can be defined an
const { Component, useState, mount } = owl;
class Greeter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ word: 'Hello' });
}
@@ -14,10 +13,9 @@ class Greeter extends Component {
// Main root component
class App extends Component {
constructor() {
super(...arguments);
this.state = useState({ name: 'World'});
}
setup() {
this.state = useState({ name: 'World'});
}
}
App.components = { Greeter };
@@ -52,8 +50,7 @@ const ANIMATION = `// The goal of this component is to see how the t-transition
const { Component, useState, mount } = owl;
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0 });
}
@@ -63,8 +60,7 @@ class Counter extends Component {
}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ flag: false, componentFlag: false, numbers: [] });
}
@@ -194,10 +190,9 @@ const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
const { Component, useState, mount } = owl;
class DemoComponent extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ n: 0 });
console.log("constructor");
console.log("setup");
}
async willStart() {
console.log("willstart");
@@ -223,8 +218,7 @@ class DemoComponent extends Component {
}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ n: 0, flag: true });
}
@@ -295,8 +289,7 @@ function useMouse() {
// Main root component
class App extends owl.Component {
constructor() {
super(...arguments);
setup() {
// simple state hook (reactive object)
this.counter = useState({ value: 0 });
@@ -333,8 +326,7 @@ const { Component, Context, mount } = owl;
const { useContext } = owl.hooks;
class ToolbarButton extends Component {
constructor() {
super(...arguments);
setup() {
this.theme = useContext(this.env.themeContext);
}
@@ -468,8 +460,7 @@ const actions = {
// TodoItem
//------------------------------------------------------------------------------
class TodoItem extends Component {
constructor() {
super(...arguments);
setup() {
useAutofocus("input");
this.state = useState({ isEditing: false });
this.dispatch = useDispatch();
@@ -499,8 +490,7 @@ class TodoItem extends Component {
// TodoApp
//------------------------------------------------------------------------------
class TodoApp extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ filter: "all" });
this.todos = useStore(state => state.todos);
this.dispatch = useDispatch();
@@ -1026,8 +1016,7 @@ class FormView extends owl.Component {}
FormView.components = { AdvancedComponent };
class Chatter extends owl.Component {
constructor() {
super(...arguments);
setup() {
this.messages = Array.from(Array(100).keys());
}
}
@@ -1170,8 +1159,7 @@ const SLOTS = `// We show here how slots can be used to create generic component
const { Component, useState, mount } = owl;
class Card extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ showContent: true });
}
@@ -1181,8 +1169,7 @@ class Card extends Component {
}
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({val: 1});
}
@@ -1193,8 +1180,7 @@ class Counter extends Component {
// Main root component
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({a: 1, b: 3});
}
@@ -1306,8 +1292,7 @@ class SlowComponent extends Component {
class NotificationList extends Component {}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0, notifs: [] });
}
@@ -1381,8 +1366,7 @@ const FORM = `// This example illustrate how the t-model directive can be used t
const { Component, useState, mount } = owl;
class Form extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({
text: "",
othertext: "",
@@ -1556,8 +1540,7 @@ const { useRef } = owl.hooks;
class HelloWorld extends Component {}
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0 });
}
@@ -1610,8 +1593,7 @@ class Window extends Component {
}
class WindowManager extends Component {
constructor() {
super(...arguments);
setup() {
this.windows = [];
this.nextId = 1;
this.currentZindex = 1;
@@ -1661,8 +1643,7 @@ class WindowManager extends Component {
WindowManager.components = { Window };
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.wmRef = useRef("wm");
}