Building a component based app with Vue
The most recent answer to build and scale complex web applications are components, divide an interface in smaller and autonomous blocks to conquer maintainability and scalability. I'm going to explore how developing a component-based app with Vue looks like and the current state of its ecosystem.
This writing belongs to a serie of articles about using components with Vue, React, Polymer and Angular 2.
In this case I will go through my thoughts and feelings on developing a web application using Vue, a young library that has gained a lot of popularity in recent times.
Introduction to Vue
The primary goal of Vue is to easily create interfaces which react on data changes, so when a Vue instance to define a template and a model.
Inside these templates you can interpolate properties with curly braces like you do in popular templating engines or use attributes.
<div id="#view">
<!-- string interpolation -->
<h1>{{ name }}</h1>
<!-- binding syntax -->
<p v-text="description"></p>
</div>
It could be said each instance acts as some kind of view controller object.
new Vue({
el: '#view',
data: {
name: 'John Oliver',
description: 'Comedian, political commentator and tv host.'
}
});
Another axiom from Vue is to let JavaScript rule the whole thing, so you can use HTML tags just as placeholders and define everything, even the template, in your instance object.
You can also bind methods and compute properties.
// HTML
<app></app>;
// JavaScript
new Vue({
el: 'app',
template: `
<div id="view">
<h1></h1>
<button @click="showDescription">Description</button>
</div>
`,
data: {
firstName: 'John',
lastName: 'Oliver',
description: 'Comedian, political commentator and tv host.'
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
},
methods: {
showDescription() {
alert(this.description);
}
}
});
The library is really well documented, one of the things I like the most about it. Give the guide a quick read which details methods, features available and also explains how it works under the hood.
Writing components
I find its component syntax the strong selling point of the library.
Just as you create Vue instances you can create Vue components to be reused.
// search form component
Vue.component('search-box', {
template: `
<form action="?" class="search__form" @submit="onSearch">
<input type="text" class="search__input"
v-model="title">
<button type="submit"
class="search__button"
:disabled="searching">Search</button>
</form>
`,
data() {
return {
title: '',
searching: false
};
},
methods: {
onSearch() {
const BASE_URL = '//www.omdbapi.com/?r=json';
this.searching = true;
fetch(`${BASE_URL}&s=${this.title}`)
.then((response) => response.json())
.then((data) => {
this.searching = false;
// do something with the data
});
}
}
});
Calling the component
method requires first a string indicating a custom tag in the kebab case and a constructor object.
Inside the template you might notice :disabled
which is just a shortcut for v-bind:disabled
directive and the v-model
binding, super useful to synchronize a form element value with data.
The @
symbol is used to bind events from methods
.
Instead of an object, data
is a function returning one to prevent all instances from sharing the same object reference causing undesired collisions.
Now the search-box tag is available for use on other Vue instance template.
new Vue({
el: `#app`,
template: `
<h1>Search</h1>
<h2>Movies</h2>
<search-box></search-box>
`
});
Props
When the component's model is partially or totally present in its parent, props
are used to pass down that information.
Vue.component('result', {
template: `
<h3 v-text="title"></h3>
<a :href="url">Find out more</a>
`,
props: ['title', 'url']
});
Then specify a prop value with Vue's binding syntax.
Vue.component('results-list', {
template: `
<ul>
<li v-for="r in results">
<result :title="r.title" :url="r.url">
</li>
</ul>
`,
data() {
return {
results: [
{
title: 'Batman Begins',
url: '//www.imdb.com/title/tt0372784'
},
{
title: 'The Dark Knight',
url: '//www.imdb.com/title/tt0468569'
},
{
title: 'The Dark Knight Rises',
url: '//www.imdb.com/title/tt1345836'
}
]
};
}
});
Props are prefixed with a colon because we are passing a data reference, but it should be removed when passing the value itself.
Styles
There is still a discussion going on about extending the components philosophy to styles, probably one of the pain points of atomizing the user interface since almost all approaches force you to mix unnatural CSS syntax with JavaScript.
New standards like HTML imports and Shadow DOM are preparing the ground for a future where native web components will be easy to develop and cross browser compatible.
In the meantime, devs are adding steps in their build processes to transform their stylesheets into local scoped CSS for pure JavaScript components.
Routing
Vue capabilities can be extended with plugins calling the use
method.
var Vue = require('vue');
var VueRouter = require('vue-router');
Vue.use(VueRouter);
On our first examples calling new Vue
meant kicking off the whole project, but when vue-router comes to play you need to first create components, the views, main Vue instance and delegate that kick start to the router.
// views as components
var mainView = Vue.component({
template: `
<p>Main view content.</p>
`
});
var aboutView = Vue.component({
template: `
<p>About view content.</p>
`
});
// main instance
var app = Vue.component({
template: `
<h1>Single Page Application</h1>
<a v-link="{ path: '/' }">Home</a>
<a v-link="{ path: '/about' }">About</a>
<router-view></router-view>
`
});
// create router
var router = new VueRouter({ root: '/' });
// map paths and views
router.map({
'/': {
component: mainView,
name: 'main'
},
'/about': {
component: aboutView,
name: 'about'
}
});
// kick off!
router.start(app, '#app');
There's no need to learn anything special for views creation, they are components, the difference is they are now passed to the router to be defined as views with an associated path.
A special directive, v-link
allows anchors to navigate the app and the router-view tag is the placeholder where the view's template will be mounted.
Finally, map the different paths and send to router.start
the app instance and the selector to initialize the project.
Ecosystem
Vue has a wider response to the styles problem with a set of tools and packages to also improve the development experience while using the framework and accelerate the ramp up for new developers.
Single file components
The problem with templates and styles in component libraries is that you end up writing CSS or HTML inside a JavaScript file, something you can get used to but isn't the best experience.
React, for example, uses JSX to fix the HTML-in-JavaScript thing.
Vue's single file components is probably a bigger bet, it allows you to put everything regarding a component in one place solving a major number of decisions around architecture out of the box.
<template>
<form action="?" class="search__form" @submit="onSearch">
<input type="text" class="search__input"
v-model="title">
<button type="submit"
class="search__button"
:disabled="searching">Search</button>
</form>
</template>
<script>
import search from '../services/search.js';
export default {
data() {
return {
title: '',
searching: false
}
},
methods: {
onSearch() {
search(this.title)
.then(data => {
this.searching = false;
// do something with the data
});
}
}
}
</script>
<style lang="less" scoped>
.search__form {
font-size: 16px;
button {
background-color: orange;
color: white;
}
}
</style>
Basically a single file component consists of three parts. What it used to be a string property is now just plain HTML inside a template tag. In scripts you export the component object constructor, and you can also import other modules.
The styles' component paradigm here is solved with a style tag where you can write using any preprocessor passing a lang
attribute.
Placing a scoped
attribute causes the styles to affect the component only, similar to generating CSS modules with Webpack loaders.
You can later import .vue files to let components be used by other instances.
<template>
<h1>Movies</h1>
<h2>Search</h2>
<search-box></search-box>
</template>
<script>
import searchBox from 'search-box.vue';
export default {
components: {
searchBox
}
}
</script>
Of course, the .vue extension is not a valid module you can import.
To transpile this to something the browser can actually render, a loader for Webpack and a transform for Browserify are available.
CLI
Vue's ecosystem and its single file components are great, thank you, but now you have to deal with building configuration?
JavaScript fatigue was and still is a real thing. Folder structure, building processes, tooling are big decisions which define how your project will scale on time.
Thankfully a command line and official templates are available to quickly start developing, testing and serving a Vue project.
Creating a terminal client is becoming a trend, giving developers the chance to adopt new environments more easily. A Polymer toolbox was announced in the last Google I/O and Angular 2 will have a command line interface too.
For some reason, there's no such a thing from the React community yet.
Wrap-up
Thanks to the single file components solution Vue's learning curve is not that steep and it embraces some look and feel from combining HTML imports and native web components.
Definitely worth giving it a try, I actually did and created a simple web app using all of the tools mentioned in this article.
As any other framework, its ecosystem kind of forces you to do things you might or might not feel comfortable doing, but if you do, Vue speeds up adoption and simplifies a lot of decisions when building large scale apps.
Do you want me to write for your publication? Click here to contact me via email.