[DOC] improve the documentation

closes #340
closes #344
closes #352
closes #355
This commit is contained in:
Géry Debongnie
2019-10-20 09:01:38 +02:00
parent 1bb4577ec1
commit 29c2c5b9c9
7 changed files with 122 additions and 91 deletions
+32 -30
View File
@@ -91,22 +91,22 @@ find a template with the component name (or one of its ancestors).
### Reactive system
OWL components are normal javascript classes. So, changing a component internal
OWL components are normal javascript classes. So, changing a component internal
state does nothing more:
```js
class Counter extends Component {
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
state = {value: 0};
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
state = { value: 0 };
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
```
Clicking on the `Counter` component defined above will call the `increment`
method, but it will not rerender the component. To fix that, one could add an
method, but it will not rerender the component. To fix that, one could add an
explicit call to `render` in `increment`:
```js
@@ -122,7 +122,7 @@ method.
A better way is to use the reactive system: by using the `useState` hook (see the
[hooks](hooks.md) section for more details), one can make Owl react to state
changes. The `useState` hook generates a proxy version of an object
changes. The `useState` hook generates a proxy version of an object
(this is done by an [observer](observer.md)), which allows the component to
react to any change. So, the `Counter` example above can be improved like this:
@@ -130,31 +130,32 @@ react to any change. So, the `Counter` example above can be improved like this:
const { useState } = owl.hooks;
class Counter extends Component {
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
state = useState({value: 0});
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
state = useState({ value: 0 });
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
```
Obviously, we can call the `useState` hook more than once:
```js
const { useState } = owl.hooks;
class Counter extends Component {
static template = xml`
static template = xml`
<div>
<span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
<span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
</div>`;
counter1 = useState({value: 0});
counter2 = useState({value: 0});
counter1 = useState({ value: 0 });
counter2 = useState({ value: 0 });
increment(counter) {
counter.value++;
}
increment(counter) {
counter.value++;
}
}
```
@@ -169,7 +170,7 @@ to be called in the constructor.
- **`env`** (Object): the component environment, which contains a QWeb instance.
- **`props`** (Object): this is an object containing all the properties given by
the parent to a child component. For example, in the following situation,
the parent to a child component. For example, in the following situation,
the parent component gives a `user` and a `color` value to the `ChildComponent`.
```xml
@@ -182,7 +183,7 @@ to be called in the constructor.
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
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`.
@@ -405,8 +406,8 @@ component was patched. Note that this hook will not be called if the compoent is
not in the DOM (this can happen with components with `t-keepalive`).
Updating the component state in this hook is possible, but not encouraged.
One need to be careful, because updates here will cause rerender, which in
turn will cause other calls to patched. So, we need to be particularly
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.
#### `willUnmount()`
@@ -429,7 +430,7 @@ This is the opposite method of `mounted`.
The `catchError` method is useful when we need to intercept and properly react
to (rendering) errors that occur in some sub components. See the section on
[error handling](#error-handling)
[error handling](#error-handling).
### Root Component
@@ -783,7 +784,7 @@ Like event handling, the `t-model` directive accepts some modifiers:
| Modifier | Description |
| --------- | -------------------------------------------------------------------- |
| `.lazy` | update the value on the `change` event (default is on `input` event) |
| `.number` | tries to parse the value to a number (using `parseFloat`) |
| `.number` | try to parse the value to a number (using `parseFloat`) |
| `.trim` | trim the resulting value |
For example:
@@ -799,9 +800,10 @@ Note: the online playground has an example to show how it works.
### `t-key` Directive
Even though Owl tries to be as declarative as possible, some DOM state is still
locked inside the DOM: for example, the scrolling state, the current user selection,
the focused element or the state of an input. This is why we use a virtual dom
Even though Owl tries to be as declarative as possible, the DOM does not fully
expose its state declaratively in the DOM tree. For example, the scrolling state,
the current user selection, the focused element or the state of an input are not
set as attribute in the DOM tree. This is why we use a virtual dom
algorithm to keep the actual DOM node as much as possible. However, this is
sometimes not enough, and we need to help Owl decide if an element is actually
the same, or is different. The `t-key` directive is used to give an identity to an element.
@@ -913,7 +915,7 @@ Here is what Owl will do:
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
- hard to tell how a component should be used, by looking at its code.
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parent.
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
A props type system would solve both issues, by describing the types and shapes
of the props. Here is how it works in Owl:
@@ -1169,7 +1171,7 @@ from lifecycle hooks): the `catchError` hook.
Whenever the `catchError` lifecycle hook is implemented, all errors coming from
sub components rendering and/or lifecycle method calls will be caught and given
to the `catchError` method. This allow us to properly handle the error, and to
to the `catchError` method. This allows us to properly handle the error, and to
not break the application.
For example, here is how we could implement an `ErrorBoundary` component:
+1 -1
View File
@@ -11,7 +11,7 @@
## Overview
The `Context` object provides a way to share data between an arbitrary number
of component. Usually, data is passed from a parent to its children component,
of components. Usually, data is passed from a parent to its children component,
but when we have to deal with some mostly global information, this can be
annoying, since each component will need to pass the information to each children,
even though some or most of them will not use the information.
+24 -24
View File
@@ -220,18 +220,18 @@ be ready whenever the component is rendered:
```js
function useLoader() {
const component = Component.current;
const record = useState({});
onWillStart(async () => {
const recordId = component.props.id;
Object.assign(record, await fetchSomeRecord(recordId));
});
return record;
const component = Component.current;
const record = useState({});
onWillStart(async () => {
const recordId = component.props.id;
Object.assign(record, await fetchSomeRecord(recordId));
});
return record;
}
```
Note that this example does not update the record value whenever props are
updated. For that situation, we need to use the `onWillUpdateProps` hook.
updated. For that situation, we need to use the `onWillUpdateProps` hook.
### `onWillUpdateProps`
@@ -241,17 +241,17 @@ useful to perform some asynchronous task such as fetching updated data.
```js
function useLoader() {
const component = Component.current;
const record = useState({});
const component = Component.current;
const record = useState({});
async function updateRecord(id) {
Object.assign(record, await fetchSomeRecord(id));
}
async function updateRecord(id) {
Object.assign(record, await fetchSomeRecord(id));
}
onWillStart(() => updateRecord(component.props.id));
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
onWillStart(() => updateRecord(component.props.id));
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
return record;
return record;
}
```
@@ -359,7 +359,7 @@ getters. See the [store documentation](store.md) for more information.
### Making customized hooks
Hooks are a wonderful way to organize the code of a complex component by feature
instead of by lifecycle methods. They are like mixins, except that they can be
instead of by lifecycle methods. They are like mixins, except that they can be
easily composed together.
But, like every good things in life, hooks should be used with moderation. They are
@@ -373,16 +373,16 @@ not the solution to every problem.
// maybe overkill
class A extends Component {
constructor(...args) {
super(...args);
useMySpecificHook()
super(...args);
useMySpecificHook();
}
}
// ok
class B extends Component {
constructor(...args) {
super(...args);
this.performSpecificTask()
super(...args);
this.performSpecificTask();
}
}
```
@@ -391,7 +391,7 @@ not the solution to every problem.
- they may be harder to test: if a customized hook injects some external side
effect dependency, then it is harder to test without doing some non obvious
manipulation. For example, assume that we want to give a reference to a
manipulation. For example, assume that we want to give a reference to a
router in a `useRouter` hook. We could do this:
```js
@@ -402,7 +402,7 @@ not the solution to every problem.
}
```
As you can see, this does not *hook* into the internal of the component. It
As you can see, this does not _hook_ into the internal of the component. It
simply returns a global object, which is difficult to mock.
A better way would be to do something like this: get the reference from the
@@ -410,7 +410,7 @@ not the solution to every problem.
```js
function useRouter() {
return Component.current.env.router;
return Component.current.env.router;
}
```
+38 -6
View File
@@ -1,10 +1,16 @@
# 🦉 Observer 🦉
Owl needs to be able to react to state changes. For example, whenever the state
of a component is changed, we need to rerender it. To help with that, we have
an Observer class. Its job is to observe some object state, and react to any
change. To do that, it recursively replaces all keys of the observed state by
getters and setters.
of a component is changed, Owl needs to rerender it. To help with that, there is
an Observer class. Its job is to observe the state of an object (or array), and
to react to any change. The observer is implemented with the native `Proxy`
object. Note that this means that it will not work on older browsers.
Note that the `Observer` is used by the `useState` and `useContext` hooks. This
is the way most Owl applications will create observers. For the majority of
use cases, there is no need to directly instantiate an observer.
## Example
For example, this code will display `update` in the console:
@@ -16,5 +22,31 @@ const obj = observer.observe({ a: { b: 1 } });
obj.a.b = 2;
```
The observer is implemented with the native `Proxy` object. Note that this
means that it will not work on older browsers.
This example shows that an observer can observe nested properties.
## Reference
**observe** An observer can observe multiple values with the `observe` method.
This method takes an object or an array as its argument and will return a proxy
(which is mapped to the initial object/array). With this proxy, the observer
can detect whenever any internal value is changed.
**Registering a callback** Whenever an observer sees a state change, it will
call its `notifyCB` method. No additional information is given to the callback.
**deepRevNumber** Each observed value has an internal revision number, which
is incremented every time the value is observed. Sometimes, it can be useful
to obtain that number:
```js
const observer = new owl.Observer();
const obj = observer.observe({ a: { b: 1 } });
observer.deepRevNumber(obj.a); // 1
obj.a.b = 2;
observer.deepRevNumber(obj.a); // 2
```
The `deepRevNumber` can also return 0, which indicates that the value is not
observed.
-1
View File
@@ -44,7 +44,6 @@ owl
whenReady
```
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
## Reference
+5 -7
View File
@@ -38,14 +38,14 @@ const actions = {
state.todos.push({
id: state.nextId++,
message,
isCompleted: false,
isCompleted: false
});
}
};
const state = {
todos: [],
nextId: 1,
nextId: 1
};
const store = new owl.Store({ state, actions });
@@ -113,6 +113,7 @@ const actions = {
```
The first argument to an action method is an object with four keys:
- `state`: the current state of the store content,
- `dispatch`: a function that can be used to dispatch other actions,
- `getters`: an object containing all getters defined in the store,
@@ -200,8 +201,7 @@ const post = store.getters.getPost(id);
Getters take _at most_ one argument.
Note that getters are cached if they don't take any argument, or their argument
is a string or a number.
Note that getters are not cached.
### Connecting a Component
@@ -212,7 +212,6 @@ can be done with the help of the three store hooks:
- `useDispatch` to get a reference to a dispatch function,
- `useGetters` to get a reference to the getters defined in the store.
Assume we have this store:
```javascript
@@ -223,7 +222,7 @@ const actions = {
};
const state = {
counter: {value: 0}
counter: { value: 0 }
};
const store = new owl.Store({ state, actions });
```
@@ -256,7 +255,6 @@ two arguments:
- optionally, an object with a `store` key (if we want to override the default
store) and an equality function (if we want to specialize the comparison).
If the `useStore` callback selects a sub part of the store state, the component
will only be rerendered whenever this part of the state changes. Otherwise, it
will perform a strict equality check and will update the component every time this
+22 -22
View File
@@ -12,7 +12,6 @@ functions are all available in the `owl.utils` namespace.
- [`debounce`](#debounce): limiting rate of function calls
- [`shallowEqual`](#shallowequal): shallow object comparison
## `whenReady`
The function `whenReady` returns a `Promise` resolved when the DOM is ready (if
@@ -20,7 +19,7 @@ not ready yet, resolved directly otherwise). If called with a callback as
argument, it executes it as soon as the DOM ready (or directly).
```js
Promise.all([loadFile('templates.xml'), owl.utils.whenReady()]).then(function([templates]) {
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function([templates]) {
const qweb = new owl.QWeb(templates);
const app = new App({ qweb });
app.mount(document.body);
@@ -45,6 +44,7 @@ properly reacts when it is ready. Also, it is smart: it maintains a list of urls
previously loaded (or currently being loaded), and prevent doing twice the work.
For example, it is useful for lazy loading external libraries:
```js
class MyComponent extends owl.Component {
willStart() {
@@ -55,7 +55,7 @@ class MyComponent extends owl.Component {
## `loadFile`
`loadFile` is a helper function to fetch a file. It simply
`loadFile` is a helper function to fetch a file. It simply
performs a `GET` request and returns the resulting string in a promise. The
initial usecase for this function is to load a template file. For example:
@@ -73,38 +73,38 @@ string. It does not add a `script` tag or any other side effect.
## `escape`
Sometimes, we need to display dynamic data (for example user-generated data) in
the user interface. If this is done by a `QWeb` template, it is not an issue:
the user interface. If this is done by a `QWeb` template, it is not an issue:
```xml
<div><t t-esc="user.data"/></div>
```
The `QWeb` engine will create a `div` node and add the content of the `user.data`
string as a text node, so the web browser will not parse it as html. However,
string as a text node, so the web browser will not parse it as html. However,
it may be a problem if this is done with some javascript code like this:
```js
class BadComponent extends Component {
// some template with a ref to a div
// some code ...
// some template with a ref to a div
// some code ...
mounted() {
this.divRef.el.innerHTML = this.state.value;
}
mounted() {
this.divRef.el.innerHTML = this.state.value;
}
}
```
In this case, the content of the `div` will be parsed as html, which may inject
unwanted behaviour. To fix this, the `escape` function will simply transform a
unwanted behaviour. To fix this, the `escape` function will simply transform a
string into an escaped version of the same string, which will be properly displayed
by the browser, but which will not be parsed as html (for example, `"<ok>"` is
escaped to the string: `"&lt;ok&gt;"`). So, the bad example above can be fixed
with the following change:
```js
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
```
## `debounce`
The `debounce` function is useful when we want to limit the number of times some
@@ -112,31 +112,31 @@ function/action is perfomed. For example, this may be useful to prevent issue
with people double clicking on a button.
It takes three arguments:
- `func` (function): this is the function that will be rate limited
- `wait` (number): this is the number of milliseconds that we want to use to
rate limit the function `func`
rate limit the function `func`
- `immediate` (optional, boolean, default=false): if `immediate` is true, the
function will be triggered immediately (leading edge of the interval). If false,
the function will be triggered at the end (trailing edge).
It returns a function. For example:
It returns a function. For example:
```js
const debounce = owl.utils.debounce
window.addEventListener('mousemove', debounce(doSomething, 100));
const debounce = owl.utils.debounce;
window.addEventListener("mousemove", debounce(doSomething, 100));
```
As this example shows, it is usualy useful for event handlers which are triggered
very quickly, such as `scroll` or `mousemove` events.
## `shallowEqual`
This function checks if two objects have the same values assigned to each keys:
```js
shallowEqual({a:1, b: 2}, {a:1, b:2}); // true
shallowEqual({a:1, b: 2}, {a:1, b:3}); // false
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }); // false
```
However, for performance reasons, it assumes that the two objects have the same
@@ -144,5 +144,5 @@ keys. If we are in a situation where this is not guaranteed, the following code
will work:
```js
const completeShallowEqual = (a,b) => shallowEqual(a,b) && shallowEqual(b,a);
```
const completeShallowEqual = (a, b) => shallowEqual(a, b) && shallowEqual(b, a);
```