There’s a lot of power in clean code. Clean code might not become better over time, the same way that a fine wine would, but at least it remains understandable as time goes by.
Spaghetti code, on the other hand, the more time goes by, the worse it becomes. You might be able to handle the chaos for now, but what if you work on something else and come back to it three, five, or 20 months later?
One of the main principles I like to stick to as a programmer, including in Vue.js projects, is the DRY principle.
DRY stands for don’t repeat yourself, and its goal is to reduce repetition.
The reason why I insist it’s important is that WET _(write everything twice) _solutions tend to introduce additional complexity in many cases. I’ll share a simple example to help you understand.
Let’s say you have duplicate code in two places in your code base. What if you decide to change the logic or some of the parameters? You’ll have to refactor your code in both places. How bad can that be, though?
Keeping your code clean can protect you from many issues, and it can also save you a lot of time. It might not seem obvious in the short term, but in the long term, clean code is the far superior option in terms of productivity.
The main way to create reusable methods, data attributes, computed properties, and more in Vue is to create mixins.
If you’re reusing logic across components, it makes sense to define it inside mixins that you can use throughout your application.
Let’s say your application has multiple routes, and by using vue-router
, you connect each route with a corresponding component.
- App.vue
-- Home.vue
-- Analytics.vue
-- Reports.vue
And now let’s assume you’ve created a custom product tour that you want to start whenever a user enters any of the tabs.
If you’re not using mixins and you want the product tour to appear on all pages, you’ll need to copy the same code into each mounted
callback and the startProductTour
method as well. I’ve written the Home.vue
example here, but you’d need the same code inside Analytics.vue
and Reports.vue
.
export default {
mounted() {
this.startProductTour();
}.
methods: {
startProductTour() {
// start product tour actions here
}
}
}
But if you define the above as a mixin…
ProductTour** mixin**
export default {
mounted() {
this.startProductTour();
}.
methods: {
startProductTour() {
// start product tour actions here
}
}
}
…then you can use it in all of your components, wherever it’s needed with a single line of code. And if you change the product, you only need to update your mixin once instead of editing multiple files.
import ProductTourMixin from '../mixins/ProductTourMixin';
export default {
mixins: [ProductTourMixin]
}
In this case, your new project structure would look like this:
- App.vue
- mixins/ProductTourMixin
-- Home.vue
-- Analytics.vue
-- Reports.vue
By using mixins, you’ve saved time and effort and have avoided headaches — just by keeping things clean and DRY.
In one of my first Vue applications, my components would grow to have hundreds or even thousands of lines of code. My giga components would do everything inside, including a massive HTML <template>
, many methods, and a lot of JavaScript within those methods. The problem with that is:
The point is clear I believe — creating one, or a few, large components instead of many small ones can be an absolute nightmare.
So what are the benefits of creating many small components instead of fewer large ones?
In a large application, it’s necessary to divide the whole app into components to make development manageable.
This quote is from the official Vue documentation on components, and it states the importance of splitting your app into components.
**Note: **Reading the entire documentation of your favorite frameworks and technologies can reveal a treasure trove of information, so I suggest you do it frequently.
Validating your props is an important part of making your Vue application maintainable.
First, let’s look at the wrong way of defining props in a component.
export default {
props: ['myProp'],
}
This way of defining props is only acceptable during development, but before going live, you should write full definitions for your props. At the very minimum, you should define a type for your props.
// quick way
export default {
props: {
myProp: String,
}
}
// this should be preferred
export default {
props: {
myProp: {
type: String
}
}
}
But the best way is to fully define your props and validate them.
props: {
myProp: {
type: Number,
required: true,
validator: (value) => {
return value > 0;
}
}
}
By validating your props, you make sure to avoid issues that are related to passing unexpected values, and it’ll help you catch such errors during development.
As an extra advantage, by having well-defined props, you’ll always be able to look at your prop definitions and validations to understand what values are expected. Think of it as a minidocumentation of what the component expects. Sometimes it’s better to have well-defined inputs, with the right names semantically than a ton of comments.
Once again, clean code beats sloppy code, allowing you to avoid errors and remember the purpose of the prop at any time.
Vue doesn’t have services like Angular, which allows you to create services to interact with APIs and build common application logic.
In my experience, it pays off to decouple presentation logic from application logic as much as possible.
To better illustrate my idea, I’ll share an example. Let’s say you have a Reports
component that includes three child components: ReportTable
, ReportChart
, and ReportStats
.
<!-- This example is intentionally kept simple -->
<template>
<report-stats :stats="stats" />
<report-chart :chartData="chartData" />
<report-table :breakdown="breakdown" />
</template>
<script>
export default {
data() {
return {
stats: null,
chartData: null,
breakdown: null
}
}
}
</script>
This is the foundation of your Reports
component. Now you’d need a way to get the data from your API, so you’d have something like this:
mounted() {
this.getStats();
}
And then you’d have something like this:
methods: {
getStats() {
axios.get('/api/to-get/the-data')
.then(response => {
this.stats = response.data.stats
});
}
}
Now that’s definitely not bad, and in smaller applications, it may actually be fine. But decoupling the application logic from the presentation logic has multiple benefits:
So how do we turn our example into a service?
const ReportsService = {
stats: {
index: async (/* params */) => {
const response = await axios.get('/api-link');
return response.data;
}
}
}
export default ReportsService;
import ReportsService from '../services/ReportsService';
export default {
methods: {
async getStats() {
this.stats = await ReportsService.stats.index(/* params */);
}
}
}
Using simple logic, we created a service that’s able to interact with our backend API or implement application logic. Our Vue component, then, is able to interact with our service, get the data it needs, and pass it to the component, _without _knowing any more details about our API or application logic.
It’d be beyond the scope of this article to go deep into the Vue style guide. In general, it’s important to learn how the frameworks you’re using are designed and how they expect you to work with them.
The creators of the framework have the right experience to make style guidelines, but since the community is likely to follow the same guidelines it’s important to follow them as well for consistency.
Some of the most important style-guide rules are the following:
This is the wrong way of doing things:
export default {
data: {
value: 1,
}
}
And this is the right way:
export default {
data() {
return {
value: 1,
}
}
}
The reason why you must use a function and not an object is that using an object would make the data
property mutable by all of the instances of the same component.
All your v-for
loops must have a key passed down because it helps Vue render your lists more efficiently.
<my-item
v-for="item in items"
:key="item.id"
:item="item" />
<!-- or with HTMl elements -->
<div
v-for="item in items"
:key="item.id"
>
{{ item.value }}
</div>
v-if
on the same element as v-for
The documentation is pretty clear as to why this is a bad idea. Not following this has negative outcomes on performance — as Vue will need to rerender the entire list, even when the items
array hasn’t changed.
I’ll explain what you can do alternatively and why it’s better.
Instead of…
<div v-for="item in items" v-if="item.shouldShow()">
{{ item.value }}
</div>
…you should do the following:
<div v-for="item in visibleItems">
{{ item.value }}
</div>
And add this to your computed
properties:
visibleItems() {
return this.items.filter(item => {
return item.shouldShow();
});
}
The benefits of doing this are the following:
visibleItems
is only reevaluted when there are changes in the items
array.v-for
has to only process the visibleItems
array, instead of the entire items
array, which makes rendering more efficient.For further reading on the style guide, you can check out the official section of the Vue documentation. Even if you’re an experienced Vue developer, you may have a lot to learn from reading it.
Writing clean and maintainable code will make life easier for whoever works with you. But it will also make your own life better as you’ll have fewer spaghetti-code messes to deal with.
If you’re in a position where you’re doing code reviews, you should take a lot of the above into account to make sure your team is deploying high-quality code and maintaining high productivity in the long term.
Thank you for reading this far!
☞ Vue js Tutorial Zero to Hero || Brief Overview about Vue.js || Learn VueJS 2023 || JS Framework
☞ Learn Vue.js from scratch 2018
☞ Is Vue.js 3.0 Breaking Vue? Vue 3.0 Preview!
☞ Vue.js Tutorial: Zero to Sixty