React vs. Vue (2)

2020-08-02

Part II - Vue

I was several weeks into React development when I decided to switch to Vue. At that point, I had finished only a part of my application. I was recreating an existing (old) PHP web application containing data input forms, calendars, reports and such. Progress was slow. I didn’t get the productivity from React that I had hoped for. Perhaps it was me. But then again… I had completed several weeks of practical training and I was using React on and off for over 2 years. So I decided to give Vue a try, which was -at the time- completely new to me. As I was in a hurry to get actual work done, I did not spend too much time with technical instructions and training. I read through the official manuals and started recreating the same data forms once again. This time, I was pleasantly surprised.

Not only was it possible for me to pick up Vue on the go, but I also managed to do it without creating horrible code. In part, this was facilitated by the Vue documentation, which is concise and to the point. But to the greater extent, it was due to the design of the Vue framework itself. Creating a Vue application feels closer to traditional web development. The framework eschews boilerplate code and provides a powerful programming model. The so-called single file Vue component consists of three parts, a template consisting of HTML or XML, a Javascript section, and a CSS section. This is all contained in a single file, which makes for cohesive code that is clearly arranged. Thanks to webpack, the individual sections can directly be loaded into static assets, just as normal HTML/JS/CSS without transpiling, which means you can hot-reload components as you develop them. Vue offers a CLI to that end which is on par with the create-react-app CLI.

React vs Vue

Conceptually, a Vue component is an object that inherits from the Vue base class. As such, it is “supercharged” with functionality made available via instance methods and properties. This notion takes a bit of getting used to, as there various declarative sections, such as data, methods, computed, etc. which the programmer defines as objects. These comprise the building blocks of a component. For example, internal state goes into the data object, behaviour goes into methods, computed properties go into computed, and listeners that respond to state changes go into watch. The presence of these so-called watchers is interesting, as they provide a mechanism for declarative reactive programming without having to create and maintain data stores and dispatchers. The component accesses all information via the this keyword, which means even though properties, state and behaviour are declared within different scopes, they are all injected into the same instance. Setters and getters are created behind the scenes for reactivity so that rerendering is triggered automatically.

Just as with any other modern UI framework, a Vue application can be visualized as a hierarchy of components. Components are composable and inheritance plays only a minor role in the application design. At the top of this hierarchy are views. Views are complete web pages and although they have a navigable URL, their implementation is exactly the same as that of a regular component. The router is an extra library that needs to be added to Vue, which is again similar to React. Vue provides a plugin mechanism for this purpose. There are many stock plugins, such as UI/widget libraries, authentication, internationalisation, and whatnot. You can also create your own plugins which is quite easy to do. Just ot prove this point, you find a minimal i18n plugin implementation at the end of this article. The router plugin is perhaps the most frequently used in SPA design. With Vue, however, you are not limited to SPAs. You can also create traditional multi page web applications or even server-side rendered (SSR) applications.

We had previously talked about the unidirectional dataflow that underlies React. Vue is based on the same principle. Props are read-only and data always flows from parent to child elements. To that end, the state data of a parent element becomes the prop of a child element and props are implicitly read-only and cannot be modified by its owner. An application will generally follow this design principle to keep things from getting overly complex. However, there is a notable exception, namely input bindings that are based on the v-model construct. You can attach any variable to a v-model and this variable is then allowed to be mutated by the child component. As mentioned, this is mainly used for edit/input components. Effectively, it’s just the same as passing a mutation handler from the parent to the child, but in my opinion it is syntactically cleaner. A great example of knowing when to break the rules for the described use case.

For some reason, state management feels a lot more natural in Vue. One reason is perhaps that reactivity happens behind the scenes. Updates normally don’t require carefully coded dispatchers. For example, there isn’t any need to invoke setState() each time a rerender is required. Perhaps the most important reason is that component state can easily be tied to an external Javascript object. This makes it possible to control props or state of various components within the application by common objects using state coupling. Obviously this should be used with caution, because it also works in the other direction and makes state changes opaque and difficult to trace. A better solution is to create a data store object with its own mutator function. If this still does not satisfy your needs, or if your application has grown very large and complex, nothing stops you from adding a Flux library. This could either be a Redux implementation for Vue, or if you have (like me) a distaste for the Redux terminology, it could be something simpler yet powerful such as Vuex.

Let’s end this brief introduction of Vue with a code example. It’s the same as in the preceding React article: an extremely simple self-updating clock component. Like its counterpart, it updates every second and displays a tooltip element with the current date on mouseover. The clock is started in the created lifecycle method. The underlying JS timer is properly deallocated when the component is destroyed in the lifecycle method of the same name. There is no need for explicit updates, as the component rerenders automatically when any of the variables inside data changes.

Example: self-updating clock component with local state using Vue lifecycle methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">{{ localTimeDate.toLocaleTimeString() }}</span>
</template>
<span>{{ localTimeDate.toLocaleDateString() }}</span>
</v-tooltip>
</template>

<script>
export default {
name: 'SimpleClock',
data() {
return {
localTimeDate: new Date(),
clock: null
}
},
created() {
this.clock = setInterval(() => { this.localTimeDate = new Date() }, 1000)
},
destroyed() {
clearInterval(this.clock)
}
}
</script>

Example: i18n feature designed as a Vue plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let i18nCatalog = new Map()
let currentLocale = 'en'
import('../i18n/en').then(catalog => {
i18nCatalog = catalog.default
})

export default {
install(Vue) {

const i18n = function(sourceStr) {
if (typeof sourceStr !== 'string') {
return sourceStr
}
let translatedStr = i18nCatalog.has(sourceStr) ? i18nCatalog.get(sourceStr) : sourceStr
return translatedStr
}

Vue.filter('i18n', i18n)

Vue.prototype.$i18n = i18n

Vue.prototype.$getLanguage = () => currentLocale

Vue.prototype.$setLanguage = locale => {
if (currentLocale) {
console.log(`Locale changed from ${currentLocale} to ${locale}`)
}
return import(`../i18n/${locale}`).then(catalog => {
i18nCatalog = catalog.default
currentLocale = locale
})
}
}
}