Currency Model
Owl was designed from the very beginning with asynchronous components. This comes
from the willStart and the willUpdateProps lifecycle hooks. With these
asynchronous hooks, it is possible to build complex highly concurrent applications.
Owl concurrent mode has several benefits: it makes it possible to delay the rendering until some asynchronous operation is complete, it makes it possible to lazy load libraries, while keeping the previous screen completely functional. It is also good for performance reasons: Owl uses it to only apply the result of many different renderings only once in an animation frame. Owl can cancel a rendering that is no longer relevant, restart it, reuse it in some cases.
But even though using concurrency is quite simple (and is the default behaviour), asynchrony is difficult, because it introduces an additional dimension that vastly increase the complexity of an application. This section will explain how Owl manages this complexity, how concurrent rendering works in a general way.
Rendering Components
The word rendering is a little vague, so, let us explain more precisely the process by which Owl components are displayed on a screen.
When a component is mounted or updated, a new rendering is started. It has two phases: virtual rendering and patching.
Virtual rendering
This phase represent the process of rendering a template, in memory, which creates a virtual representation of the desired component html). The output of this phase is a virtual DOM.
It is asynchronous: each subcomponents needs to either be created (so, willStart
will need to be called), or updated (which is done with the willUpdateProps
method). This is completely a recursive process: a component is the root of a
component tree, and each sub component needs to be (virtually) rendered.
Patching
Once a rendering is complete, it will be applied on the next animation frame. This is done synchronously: the whole component tree is patched to the real DOM.
Semantics
We give here an informal description of the way components are created/updated in an application. Here, ordered lists describe actions that are executed sequentially, bullet lists describe actions that are executed in parallel.
Scenario 1: initial rendering Imagine we want to render the following component tree:
A / \ B C / \ D EHere is what happen whenever we mount the root
component (with some code like app.mount(document.body)).
-
willStartis called onA -
when it is done, template
Ais rendered.- component
Bis createdwillStartis called onB- template
Bis rendered
- component
Cis createdwillStartis called onC- template
Cis rendered- component
Dis createdwillStartis called onD- template
Dis rendered
- component
Eis createdwillStartis called onE- template
Eis rendered
- component
- component
-
each components are patched into a detached DOM element, in the following order:
E,D,C,B,A. (so the actual full DOM tree is created in one pass) -
the component
Aroot element is actually appended todocument.body -
The method
mountedis called recursively on all components in the following order:E,D,C,B,A.
Scenario 2: updating a component. Now, let’s assume that the user clicked on some
button in C, and this results in a state update, which is supposed to:
- update
D, - remove
E, - add new component
F.
So, the component tree should look like this:
A / \ B C / \ D FHere is what Owl will do:
-
because of a state change, the method
renderis called onC -
template
Cis rendered again- component
Dis updated:- hook
willUpdatePropsis called onD(async) - template
Dis rerendered
- hook
- component
Fis created:- hook
willStartis called onF(async) - template
Fis rendered
- hook
- component
-
willPatchhooks are called recursively on componentsC,D(not onF, because it is not mounted yet) -
components
F,Dare patched in that order -
component
Cis patched, which will cause recursively:willUnmounthook onE- destruction of
E,
-
mountedhook is called onF,patchedhooks are called onD,C
Tags are very small helpers to make it easy to write inline templates. There is
only one currently available tag: xml.
Asynchronous Rendering
Working with asynchronous code always adds a lot of complexity to a system. Whenever different parts of a system are active at the same time, one needs to think carefully about all possible interactions. Clearly, this is also true for Owl components.
There are two different common problems with Owl asynchronous rendering model:
- any component can delay the rendering (initial and subsequent) of the whole application
- for a given component, there are two independant situations that will trigger an asynchronous rerendering: a change in the state, or a change in the props. These changes may be done at different times, and Owl has no way of knowing how to reconcile the resulting renderings.
Here are a few tips on how to work with asynchronous components:
- Minimize the use of asynchronous components!
- Lazy loading external libraries is a good use case for async rendering. This is mostly fine, because we can assume that it will only takes a fraction of a second, and only once.