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)
|
||||
- [`onWillPatch`](#onwillpatch)
|
||||
- [`onPatched`](#onpatched)
|
||||
- [`onWillStart`](#onwillstart)
|
||||
- [`onWillUpdateProps`](#onwillupdateprops)
|
||||
- [`useContext`](#usecontext)
|
||||
- [`useRef`](#useref)
|
||||
- [`useSubEnv`](#usesubenv)
|
||||
@@ -203,6 +205,59 @@ before the component patched.
|
||||
abstractions. `onPatched` registers a callback, which will be called just
|
||||
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`
|
||||
|
||||
See [`useContext`](context.md#usecontext) for reference documentation.
|
||||
|
||||
+3
-1
@@ -16,10 +16,12 @@ owl
|
||||
EventBus
|
||||
Observer
|
||||
hooks
|
||||
onWillStart
|
||||
onMounted
|
||||
onWillUnmount
|
||||
onWillUpdateProps
|
||||
onWillPatch
|
||||
onPatched
|
||||
onWillUnmount
|
||||
useContext
|
||||
useState
|
||||
useRef
|
||||
|
||||
+49
-49
@@ -9,6 +9,7 @@
|
||||
- [Actions](#actions)
|
||||
- [Getters](#getters)
|
||||
- [Connecting a Component](#connecting-a-component)
|
||||
- [`useStore`](#usestore)
|
||||
- [Semantics](#semantics)
|
||||
- [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
|
||||
arbitrary number of arguments.
|
||||
|
||||
@@ -196,76 +205,66 @@ is a string or a number.
|
||||
|
||||
### Connecting a Component
|
||||
|
||||
At some point, we need a way to access the state in the store from a component.
|
||||
By default, an Owl `Component` is not connected to any store. To do that, we
|
||||
need to create a component inheriting from `OwlComponent`:
|
||||
At some point, we need a way to interact with the store from a component. This
|
||||
can be done with the help of the three store hooks:
|
||||
|
||||
- `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
|
||||
const actions = {
|
||||
increment({ state }, val) {
|
||||
state.counter += val;
|
||||
state.counter.value += val;
|
||||
}
|
||||
};
|
||||
|
||||
const state = {
|
||||
counter: 0
|
||||
counter: {value: 0}
|
||||
};
|
||||
const store = new owl.Store({ state, actions });
|
||||
```
|
||||
|
||||
class Counter extends owl.ConnectedComponent {
|
||||
static mapStoreToProps(state) {
|
||||
return {
|
||||
value: state.counter
|
||||
};
|
||||
}
|
||||
increment() {
|
||||
this.env.store.dispatch("increment");
|
||||
}
|
||||
A counter component can then select this value and dispatch an action like this:
|
||||
|
||||
```js
|
||||
class Counter extends Component {
|
||||
counter = useStore(state => state.counter);
|
||||
dispatch = useDispatch();
|
||||
}
|
||||
|
||||
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
|
||||
<button t-name="Counter" t-on-click="dispatch('increment')">
|
||||
Click Me! [<t t-esc="props.value"/>]
|
||||
Click Me! [<t t-esc="counter.value"/>]
|
||||
</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
|
||||
|
||||
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:
|
||||
|
||||
- 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
|
||||
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
|
||||
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
|
||||
- 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
|
||||
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
|
||||
* after their parents. So, this could be optimized by being smarter, and
|
||||
* 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() {
|
||||
const id = ++this.id;
|
||||
|
||||
Reference in New Issue
Block a user