[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) - [Methods](#methods)
- [Lifecycle](#lifecycle) - [Lifecycle](#lifecycle)
- [`constructor(parent, props)`](#constructorparent-props) - [`constructor(parent, props)`](#constructorparent-props)
- [`setup()`](#setup)
- [`willStart()`](#willstart) - [`willStart()`](#willstart)
- [`mounted()`](#mounted) - [`mounted()`](#mounted)
- [`willUpdateProps(nextProps)`](#willupdatepropsnextprops) - [`willUpdateProps(nextProps)`](#willupdatepropsnextprops)
@@ -324,7 +325,7 @@ a owl component:
| Method | Description | | Method | Description |
| ------------------------------------------------ | ----------------------------------------------------------- | | ------------------------------------------------ | ----------------------------------------------------------- |
| **[constructor](#constructorparent-props)** | constructor | | **[setup](#setup)** | setup |
| **[willStart](#willstart)** | async, before first rendering | | **[willStart](#willstart)** | async, before first rendering |
| **[mounted](#mounted)** | just after component is rendered and added to the DOM | | **[mounted](#mounted)** | just after component is rendered and added to the DOM |
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update | | **[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()`
willStart is an asynchronous hook that can be implemented to willStart is an asynchronous hook that can be implemented to
+8 -1
View File
@@ -131,7 +131,7 @@ class SomeComponent extends Component {
### One rule ### One rule
There is only one rule: every hook for a component has to be called in the 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 ```js
// ok // 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 // not ok: this is executed after the constructor is called
class SomeComponent extends Component { class SomeComponent extends Component {
async willStart() { async willStart() {
+12
View File
@@ -209,8 +209,20 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (constr.style) { if (constr.style) {
this.__applyStyles(constr); 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 * willStart is an asynchronous hook that can be implemented to perform some
* action before the initial rendering of a component. * action before the initial rendering of a component.
+6 -9
View File
@@ -661,9 +661,8 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component { class ChildWidget extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
constructor(parent) { setup() {
super(parent); steps.push("setup");
steps.push("init");
} }
async willStart() { async willStart() {
steps.push("willstart"); steps.push("willstart");
@@ -687,10 +686,10 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget(); const widget = new ParentWidget();
await widget.mount(fixture); await widget.mount(fixture);
expect(steps).toEqual(["init", "willstart", "mounted"]); expect(steps).toEqual(["setup", "willstart", "mounted"]);
widget.state.ok = false; widget.state.ok = false;
await nextTick(); 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 () => { 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 { class ChildWidget extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
constructor(parent) { setup() {
super(parent);
steps.push("c init"); steps.push("c init");
} }
async willStart() { async willStart() {
@@ -755,8 +753,7 @@ describe("lifecycle hooks", () => {
class ParentWidget extends Component { class ParentWidget extends Component {
static template = xml`<div><t t-component="child"/></div>`; static template = xml`<div><t t-component="child"/></div>`;
static components = { child: ChildWidget }; static components = { child: ChildWidget };
constructor(parent?) { setup() {
super(parent);
steps.push("p init"); steps.push("p init");
} }
async willStart() { async willStart() {
+20 -39
View File
@@ -2,8 +2,7 @@ const COMPONENTS = `// In this example, we show how components can be defined an
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Greeter extends Component { class Greeter extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ word: 'Hello' }); this.state = useState({ word: 'Hello' });
} }
@@ -14,8 +13,7 @@ class Greeter extends Component {
// Main root component // Main root component
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ name: 'World'}); this.state = useState({ name: 'World'});
} }
} }
@@ -52,8 +50,7 @@ const ANIMATION = `// The goal of this component is to see how the t-transition
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Counter extends Component { class Counter extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ value: 0 }); this.state = useState({ value: 0 });
} }
@@ -63,8 +60,7 @@ class Counter extends Component {
} }
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ flag: false, componentFlag: false, numbers: [] }); 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; const { Component, useState, mount } = owl;
class DemoComponent extends Component { class DemoComponent extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ n: 0 }); this.state = useState({ n: 0 });
console.log("constructor"); console.log("setup");
} }
async willStart() { async willStart() {
console.log("willstart"); console.log("willstart");
@@ -223,8 +218,7 @@ class DemoComponent extends Component {
} }
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ n: 0, flag: true }); this.state = useState({ n: 0, flag: true });
} }
@@ -295,8 +289,7 @@ function useMouse() {
// Main root component // Main root component
class App extends owl.Component { class App extends owl.Component {
constructor() { setup() {
super(...arguments);
// simple state hook (reactive object) // simple state hook (reactive object)
this.counter = useState({ value: 0 }); this.counter = useState({ value: 0 });
@@ -333,8 +326,7 @@ const { Component, Context, mount } = owl;
const { useContext } = owl.hooks; const { useContext } = owl.hooks;
class ToolbarButton extends Component { class ToolbarButton extends Component {
constructor() { setup() {
super(...arguments);
this.theme = useContext(this.env.themeContext); this.theme = useContext(this.env.themeContext);
} }
@@ -468,8 +460,7 @@ const actions = {
// TodoItem // TodoItem
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class TodoItem extends Component { class TodoItem extends Component {
constructor() { setup() {
super(...arguments);
useAutofocus("input"); useAutofocus("input");
this.state = useState({ isEditing: false }); this.state = useState({ isEditing: false });
this.dispatch = useDispatch(); this.dispatch = useDispatch();
@@ -499,8 +490,7 @@ class TodoItem extends Component {
// TodoApp // TodoApp
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class TodoApp extends Component { class TodoApp extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ filter: "all" }); this.state = useState({ filter: "all" });
this.todos = useStore(state => state.todos); this.todos = useStore(state => state.todos);
this.dispatch = useDispatch(); this.dispatch = useDispatch();
@@ -1026,8 +1016,7 @@ class FormView extends owl.Component {}
FormView.components = { AdvancedComponent }; FormView.components = { AdvancedComponent };
class Chatter extends owl.Component { class Chatter extends owl.Component {
constructor() { setup() {
super(...arguments);
this.messages = Array.from(Array(100).keys()); 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; const { Component, useState, mount } = owl;
class Card extends Component { class Card extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ showContent: true }); this.state = useState({ showContent: true });
} }
@@ -1181,8 +1169,7 @@ class Card extends Component {
} }
class Counter extends Component { class Counter extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({val: 1}); this.state = useState({val: 1});
} }
@@ -1193,8 +1180,7 @@ class Counter extends Component {
// Main root component // Main root component
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({a: 1, b: 3}); this.state = useState({a: 1, b: 3});
} }
@@ -1306,8 +1292,7 @@ class SlowComponent extends Component {
class NotificationList extends Component {} class NotificationList extends Component {}
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ value: 0, notifs: [] }); 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; const { Component, useState, mount } = owl;
class Form extends Component { class Form extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ this.state = useState({
text: "", text: "",
othertext: "", othertext: "",
@@ -1556,8 +1540,7 @@ const { useRef } = owl.hooks;
class HelloWorld extends Component {} class HelloWorld extends Component {}
class Counter extends Component { class Counter extends Component {
constructor() { setup() {
super(...arguments);
this.state = useState({ value: 0 }); this.state = useState({ value: 0 });
} }
@@ -1610,8 +1593,7 @@ class Window extends Component {
} }
class WindowManager extends Component { class WindowManager extends Component {
constructor() { setup() {
super(...arguments);
this.windows = []; this.windows = [];
this.nextId = 1; this.nextId = 1;
this.currentZindex = 1; this.currentZindex = 1;
@@ -1661,8 +1643,7 @@ class WindowManager extends Component {
WindowManager.components = { Window }; WindowManager.components = { Window };
class App extends Component { class App extends Component {
constructor() { setup() {
super(...arguments);
this.wmRef = useRef("wm"); this.wmRef = useRef("wm");
} }