OWL, React and Vue have the same main feature: they allow developers to build declarative user interfaces. To do that, all these frameworks uses a virtual dom. However, there are still obviously many differences.
In this page, we try to highlight some of these differences. Obviously, a lot of effort was done to be fair. However, if you disagree with some of the points discussed, feel free to open an issue/submit a PR to correct this text.
- Size
- Class Based
- Tooling/Build Step
- Templating
- Asynchronous rendering
- Reactiveness
- State Management
- Hooks
OWL is intended to be small and to work at a slightly lower level of abstraction than React and Vue. Also, jQuery is not the same kind of framework, but it is interesting to compare.
Framework | Size (minified, gzipped) |
---|---|
OWL | 16kb |
Vue + VueX | 30kb |
Vue + VueX + Vue Router | 39kb |
React + ReactDOM + Redux | 40kb |
jQuery | 30kb |
Note that those comparisons are not entirely fair, because we do not compare the same exact set of features. For example, VueX and Vue Router support more advanced use cases.
Both React and Vue moved away from defining components with classes. They prefer
a more functional approach, in particular, with the new hooks
mechanisms.
This has some advantages and disadvantages. But the end result is that React and Vue both offers multiple different ways of defining new components. In contrast, Owl has only one mechanism: class-based components. We believe that Owl components are fast enough for all our usecases, and making it as simple as possible for developers is more valuable (for us).
Also, functions or class based components are more than just syntax. Functions comes with a mindset of composition and class are about inheritance. Clearly, both of these are important mechanisms for reusing code. Also, one does not exclude the other.
It certainly looks like the world of UI frameworks is moving toward composition,
for many very good reasons. Owl is still good at composition (for example,
Owl supports slots, which is the primary mechanism to make generic reusable
components). But it can also use inheritance (and this is very important since
templates can also be inherited with xpaths
transformations).
OWL is designed to be easy to use in a standalone way. For various reasons, Odoo does not want to rely on standard web tools (such as webpack), and OWL can be used by simply adding a script tag to a page.
<script src="owl.min.js" />
In comparison, React encourages using JSX, which necessitate a build step, and most Vue applications uses single file components, which also necessitate a build step.
On the flipside, external tooling may make it harder to use in some case, but it also brings a lot of benefits. And React/Vue have both a large ecosystem.
Note that since Owl is not dependant on any external tool nor libraries, it is very easy to integrate into any build toolchain. Also, since we cannot rely on additional tools, we made a lot of effort to make the most of the web platform.
For example, Owl uses the standard xml
parser that comes with every browser.
Because of that, Owl did not have to write its own template parser. Another
example is the xml
tag helper function, which makes use of
native template literals to allow in a natural way to write xml
templates
directly in the javascript code. This can be easily integrated with editor
plugins to have autocompletion inside the template.
OWL uses its own QWeb engine, which compiles templates on the frontend, as they are needed. This is extremely convenient for our use case, in particular because templates are described in XML files, and can be modified by XPaths. Since Odoo is at its heart a modular application, this is an important feature for us.
<div>
<button t-on-click="increment">Click Me! [<t t-esc="state.value"/>]</button>
</div>
Vue is actually kind of similar. Its template language is kind of close to QWeb,
with the v
replaced by the t
. However, it is also more fully featured. For
example, Vue templates have slots, or event modifiers. A large difference is that
most Vue applications will need to be built ahead of time, to compile the templates
into javascript functions. Note that Vue has a separate build which includes the
template compiler.
In contrast, most React applications do not use a templating language, but write some JSX code, which is precompiled into plain JavaScript by a build step. This example is done with the (kind of outdated) React class system:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
This has the advantage of having the full power of Javascript, but is less structured than a template language. Note that the tooling is quite impressive: there is a syntax highlighter for jsx here on github!
By comparison, here is the equivalent Owl component, written with the
xml
tag helper:
class Clock extends Component {
static template = xml`
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
`;
}
This is actually a big difference between OWL and React/Vue: components in OWL are totally asynchronous. They have two asynchronous hooks in their lifecycle:
willStart
(before the component starts rendering)willUpdateProps
(before new props are set)
Both these methods can be implemented and return a promise. The rendering will then wait for these promises to be completed before patching the DOM. This is useful for some use cases: for example, a component may want to fetch an external library (a calendar component may need a specialized calendar rendering library), in its willStart hook.
class MyCalendarComponent extends owl.Component {
...
willStart() {
return utils.lazyLoad('static/libs/fullcalendar/fullcalendar.js');
}
...
}
This may be dangerous (to stop the rendering waiting for the network), but it is extremely powerful as well, as demonstrated by the Odoo Web Client.
Lazy loading static libraries can obviously be done with React/Vue, but it is more convoluted. For example, in Vue, you need to use a dynamic import keyword that needs to be transpiled at build time in order for the component to be loaded asynchronously (see the documentation).
React has a simple model: whenever the state changes, it is
replaced with a new state (via the setState
method). Then, the DOM is patched.
This is simple, efficient, and a little bit awkward to write.
Vue is a little bit different: it replace magically the properties in the state by getters/setters. With that, it can notify components whenever the state that they read was changed.
Owl is closer to vue: it also tracks magically the state properties, but it does
only increment an internal counter whenever it changes. Note that it is done
with a Proxy
, which means that it is totally transparent to the developers.
Adding new keys is supported. Once any part of the state has been changed, a
rendering is scheduled in the next microtask tick (promise queue).
Managing the state of an application is a tricky issue. Many solutions have been proposed these last few years. It also depends on the kind of application we are talking about. A small application may not need much more than a simple object to contain its state.
However, there are some common solutions for React and Vue: redux and vuex. Both of them are a centralized store that own the state, and they dictate how the state can be mutated.
Redux
In Redux, the state is mutated by reducers. Reducers are functions that modify the state by returning a different object:
...
switch (action.type) {
case ADD_TODO: {
const { id, content } = action.payload;
return {
...state,
allIds: [...state.allIds, id],
byIds: {
...state.byIds,
[id]: {
content,
completed: false
}
}
};
}
This is a little bit awkward to write, but this allows the component system to
check if a part of the state was changed. This is exactly what is done by the
connect
function: it create a connected component, which is subscribed to
the state and triggers a rerender if some part of the state was modified.
VueX
VueX is based on a different principle: the state is mutated through some special functions (the mutations), which modify the state in place:
function ({state}, payload) {
const { id, content } = payload;
const message = {id, content, completed: false};
state.messages.push(message)
}
This is simpler, but there is a little bit more happening behind the scene: each key from the state is silently replaced by getters and setters, and VueX keeps track of who get data, and retrigger a render when it was changed.
Owl
Owl store is a little bit like a mix of redux and vuex: it has actions (but not
mutations), and like VueX, it keeps track of the state changes. However, it does
not notify a component when the state changes. Instead, components need to connect
to the store like in redux, with the useStore
hook (see the store documentation).
const actions = {
increment({ state }, val) {
state.counter.value += val;
}
};
const state = {
counter: { value: 0 }
};
const store = new owl.Store({ state, actions });
class Counter extends Component {
static template = xml`
<button t-name="Counter" t-on-click="dispatch('increment')">
Click Me! [<t t-esc="counter.value"/>]
</button>`;
counter = useStore(state => state.counter);
dispatch = useDispatch();
}
const counter = new Counter({ store, qweb });
Hooks recently took over the React world. They solve a lot of seemingly unconnected problems: attach reusable behavior to a component, in a composable way, extract stateful logic from a component or reuse stateful logic between component, without changing your component hierarchy.
Here is an example of the React useState
hook:
import React, { useState } from "react";
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Because of the way React designed the hooks API, they only work for functional components. But in that case, they really are powerful. Every major React library is in the process of redesigning their API with hooks (for example, Redux).
Vue 2 does not have hooks, but the Vue project is working on its next version, which will feature its new composition API. This work is based on the new ideas introduced by React hooks.
From the way React and Vue introduce their hooks, it may look like hooks are not
compatible with class components. However, this is not the case, as shown by
Owl hooks. They are inspired by both React and Vue. For example,
the useState
hook is named after React, but its API is closer to the reactive
Vue hook.
Here is what the Counter
example above look like in Owl:
import { Component, Owl } from "owl";
import { xml } from "owl/tags";
class Example extends Component {
static template = xml`
<div>
<p>You clicked {count.value} times</p>
<button t-on-click="increment">Click me</button>
</div>`;
count = useState({ value: 0 });
increment() {
this.state.value++;
}
}
Since the Owl framework had hooks from early in its life, its main APIs
are designed to be interacted with hooks from the start. For example, the
Context
and Store
abstractions.