mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[DOC] doc: add more information in store section
This commit is contained in:
@@ -12,6 +12,8 @@
|
|||||||
- [`onWillUnmount`](#onwillunmount)
|
- [`onWillUnmount`](#onwillunmount)
|
||||||
- [`onWillPatch`](#onwillpatch)
|
- [`onWillPatch`](#onwillpatch)
|
||||||
- [`onPatched`](#onpatched)
|
- [`onPatched`](#onpatched)
|
||||||
|
- [`onWillStart`](#onwillstart)
|
||||||
|
- [`onWillUpdateProps`](#onwillupdateprops)
|
||||||
- [`useContext`](#usecontext)
|
- [`useContext`](#usecontext)
|
||||||
- [`useRef`](#useref)
|
- [`useRef`](#useref)
|
||||||
- [`useSubEnv`](#usesubenv)
|
- [`useSubEnv`](#usesubenv)
|
||||||
@@ -203,6 +205,59 @@ before the component patched.
|
|||||||
abstractions. `onPatched` registers a callback, which will be called just
|
abstractions. `onPatched` registers a callback, which will be called just
|
||||||
after the component patched.
|
after the component patched.
|
||||||
|
|
||||||
|
### `onWillStart`
|
||||||
|
|
||||||
|
`onWillStart` is an asynchronous hook. This means that the function registered
|
||||||
|
in the hook will be run just before the component is first rendered and can return a
|
||||||
|
promise, to express the fact that it is an asynchronous operation.
|
||||||
|
|
||||||
|
Note that if there are more than one `onWillStart` registered callback, then they
|
||||||
|
will all be run in parallel.
|
||||||
|
|
||||||
|
It can be used to load some initial data. For example, the following hook will
|
||||||
|
automatically load some data from the server, and return an object that will
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this example does not update the record value whenever props are
|
||||||
|
updated. For that situation, we need to use the `onWillUpdateProps` hook.
|
||||||
|
|
||||||
|
### `onWillUpdateProps`
|
||||||
|
|
||||||
|
Just like `onWillStart`, `onWillUpdateProps` is an asynchronous hook. It is
|
||||||
|
designed to be run whenever the component props are updated. This could be
|
||||||
|
useful to perform some asynchronous task such as fetching updated data.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function useLoader() {
|
||||||
|
const component = Component.current;
|
||||||
|
const record = useState({});
|
||||||
|
|
||||||
|
async function updateRecord(id) {
|
||||||
|
Object.assign(record, await fetchSomeRecord(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
onWillStart(() => updateRecord(component.props.id));
|
||||||
|
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if there are more than one `onWillUpdateProps` registered callback,
|
||||||
|
then they will all be run in parallel.
|
||||||
|
|
||||||
### `useContext`
|
### `useContext`
|
||||||
|
|
||||||
See [`useContext`](context.md#usecontext) for reference documentation.
|
See [`useContext`](context.md#usecontext) for reference documentation.
|
||||||
|
|||||||
+3
-1
@@ -16,10 +16,12 @@ owl
|
|||||||
EventBus
|
EventBus
|
||||||
Observer
|
Observer
|
||||||
hooks
|
hooks
|
||||||
|
onWillStart
|
||||||
onMounted
|
onMounted
|
||||||
onWillUnmount
|
onWillUpdateProps
|
||||||
onWillPatch
|
onWillPatch
|
||||||
onPatched
|
onPatched
|
||||||
|
onWillUnmount
|
||||||
useContext
|
useContext
|
||||||
useState
|
useState
|
||||||
useRef
|
useRef
|
||||||
|
|||||||
+49
-49
@@ -9,6 +9,7 @@
|
|||||||
- [Actions](#actions)
|
- [Actions](#actions)
|
||||||
- [Getters](#getters)
|
- [Getters](#getters)
|
||||||
- [Connecting a Component](#connecting-a-component)
|
- [Connecting a Component](#connecting-a-component)
|
||||||
|
- [`useStore`](#usestore)
|
||||||
- [Semantics](#semantics)
|
- [Semantics](#semantics)
|
||||||
- [Good Practices](#good-practices)
|
- [Good Practices](#good-practices)
|
||||||
|
|
||||||
@@ -111,6 +112,14 @@ 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
|
||||||
|
- `env`: the current environment. This is useful sometimes, in particular if
|
||||||
|
an action needs to apply some side effects (such as performing an rpc), and
|
||||||
|
the `rpc` method is located in the environment.
|
||||||
|
|
||||||
Actions are called with the `dispatch` method on the store, and can receive an
|
Actions are called with the `dispatch` method on the store, and can receive an
|
||||||
arbitrary number of arguments.
|
arbitrary number of arguments.
|
||||||
|
|
||||||
@@ -196,76 +205,66 @@ is a string or a number.
|
|||||||
|
|
||||||
### Connecting a Component
|
### Connecting a Component
|
||||||
|
|
||||||
At some point, we need a way to access the state in the store from a component.
|
At some point, we need a way to interact with the store from a component. This
|
||||||
By default, an Owl `Component` is not connected to any store. To do that, we
|
can be done with the help of the three store hooks:
|
||||||
need to create a component inheriting from `OwlComponent`:
|
|
||||||
|
- `useStore` to subscribe a component to some part of the store state,
|
||||||
|
- `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
|
```javascript
|
||||||
const actions = {
|
const actions = {
|
||||||
increment({ state }, val) {
|
increment({ state }, val) {
|
||||||
state.counter += val;
|
state.counter.value += val;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
counter: 0
|
counter: {value: 0}
|
||||||
};
|
};
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
|
```
|
||||||
|
|
||||||
class Counter extends owl.ConnectedComponent {
|
A counter component can then select this value and dispatch an action like this:
|
||||||
static mapStoreToProps(state) {
|
|
||||||
return {
|
```js
|
||||||
value: state.counter
|
class Counter extends Component {
|
||||||
};
|
counter = useStore(state => state.counter);
|
||||||
}
|
dispatch = useDispatch();
|
||||||
increment() {
|
|
||||||
this.env.store.dispatch("increment");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const counter = new Counter({ store, qweb });
|
const counter = new Counter({ store, qweb });
|
||||||
```
|
```
|
||||||
|
|
||||||
```xml
|
|
||||||
<button t-name="Counter" t-on-click="increment">
|
|
||||||
Click Me! [<t t-esc="props.value"/>]
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
The `ConnectedComponent` class can be configured with the following fields:
|
|
||||||
|
|
||||||
- `mapStoreToProps`: a function that extracts the `props` of the Component
|
|
||||||
from the `state` of the `Store` and returns them as a dict.
|
|
||||||
- `getStore`: a function that takes the `env` in arguments and returns an
|
|
||||||
instance of `Store` to connect to (if not given, connects to `env.store`)
|
|
||||||
- `hashFunction`: the function to use to detect changes in the state (if not
|
|
||||||
given, generates a function that uses revision numbers, incremented at
|
|
||||||
each state change)
|
|
||||||
- `deep` (boolean): [only useful if no hashFunction is given] if `false`, only watch
|
|
||||||
for top level state changes (`true` by default)
|
|
||||||
|
|
||||||
Note that the class `ConnectedComponent` has a `dispatch` method. This means
|
|
||||||
that the previous example could be simplified like this:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
class Counter extends owl.ConnectedComponent {
|
|
||||||
static mapStoreToProps(state) {
|
|
||||||
return {
|
|
||||||
value: state.counter
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-name="Counter" t-on-click="dispatch('increment')">
|
<button t-name="Counter" t-on-click="dispatch('increment')">
|
||||||
Click Me! [<t t-esc="props.value"/>]
|
Click Me! [<t t-esc="counter.value"/>]
|
||||||
</button>
|
</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `useStore``
|
||||||
|
|
||||||
|
The `useStore` hook is used to select some part of the store state. It accepts
|
||||||
|
two arguments:
|
||||||
|
|
||||||
|
- a selector function, which takes the store state as first argument (and the
|
||||||
|
component props as second argument) and returns
|
||||||
|
an object or an array (which will be then observed)
|
||||||
|
- 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 updates the component every time this
|
||||||
|
check fails.
|
||||||
|
|
||||||
### Semantics
|
### Semantics
|
||||||
|
|
||||||
The `Store` and the `ConnectedComponent` try to be smart and to optimize as much
|
The `Store` class and the `useStore` hook try to be smart and to optimize as much
|
||||||
as possible the rendering and update process. What is important to know is:
|
as possible the rendering and update process. What is important to know is:
|
||||||
|
|
||||||
- components are always updated in the order of their creation (so, parent
|
- components are always updated in the order of their creation (so, parent
|
||||||
@@ -283,8 +282,9 @@ as possible the rendering and update process. What is important to know is:
|
|||||||
- avoid asynchronous components as much as possible. Asynchronous components
|
- avoid asynchronous components as much as possible. Asynchronous components
|
||||||
lead to situations where parts of the UI is not updated immediately.
|
lead to situations where parts of the UI is not updated immediately.
|
||||||
- do not be afraid to connect many components, parent or children if needed. For
|
- do not be afraid to connect many components, parent or children if needed. For
|
||||||
example, a `MessageList` component could get a list of ids in its `mapStoreToProps` and a `Message` component could get the data of its own
|
example, a `MessageList` component could get a list of ids in its `useStore`
|
||||||
|
call and a `Message` component could get the data of its own
|
||||||
message
|
message
|
||||||
- since the `mapStoreToProps` function is called for each connected component,
|
- since the `useStore` function is called for each connected component,
|
||||||
for each state update, it is important to make sure that these functions are
|
for each state update, it is important to make sure that these functions are
|
||||||
as fast as possible.
|
as fast as possible.
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ export class Context extends EventBus {
|
|||||||
* next. However, the only things that matters is that children are updated
|
* next. However, the only things that matters is that children are updated
|
||||||
* after their parents. So, this could be optimized by being smarter, and
|
* after their parents. So, this could be optimized by being smarter, and
|
||||||
* updating all widgets concurrently, except for parents/children.
|
* updating all widgets concurrently, except for parents/children.
|
||||||
|
*
|
||||||
|
* A potential cheap way to improve this situation is to keep track of the
|
||||||
|
* depth of a component in the component tree. A root component has a depth of
|
||||||
|
* 1, then its children of 2 and so on... Then, we can update all components
|
||||||
|
* with the same depth in parallel.
|
||||||
*/
|
*/
|
||||||
async __notifyComponents() {
|
async __notifyComponents() {
|
||||||
const id = ++this.id;
|
const id = ++this.id;
|
||||||
|
|||||||
Reference in New Issue
Block a user