Vue.js Tutorial
posted in javascript on • by Wouter Van Schandevijlvuejs/vue : Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
Why Vue
100k+ ⭐
: So many people can’t be wrong- Declarative rendering with a cool, terse syntax
- Low learning curve
- Reactive and composable view components
- Optional official
vue-router
andvuex
(statemanagement) @vue/cli
: Scaffold project with optional support for TypeScript, PWA, CSS Pre-processors, Linters, Tests, …- Automatic dependency tracking
- Virtual DOM
- Supports IE9+
Official Projects
vuejs/vue-router : The official router for Vue.js.
vuejs/vuex : Centralized State Management for Vue.js.
vuejs/vue-cli : Standard Tooling for Vue.js Development
vuejs/vue-devtools : ⚙ Chrome and Firefox devtools extension for debugging Vue.js applications.
vuejs/vetur : Vue tooling for VS Code.
Hello Vue
Html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<!-- Binding -->
<h1>{{ product }}</h1>
<!-- v-bind directive -->
<img v-bind:src="image" :title="imageTitle">
<!-- :src, :alt, :title, :class, :style, :disabled -->
<!-- Conditionals -->
<div>
<!-- Falsy will not include in DOM -->
<p v-if="inventory > 10">In Stock</p>
<p v-else-if="inventory">Almost sold out</p>
<p v-else>Out of Stock</p>
<!-- Render with display: none -->
<p v-show="inventory < 0">Wuuk?</p>
</div>
<!-- Events -->
<!-- v-on directive shorthand is @ -->
<button
v-on:click="cart++; inventory--"
:disabled="!inventory"
:class="inventory ? '' : 'disabledButton'"
>
Add to Cart
</button>
<div class="cart">{{cart}}</div>
<!-- Loops -->
<div v-for="variant in variants"
:key="variant.id"
:style="{backgroundColor: variant.color}"
@mouseover="setImage(variant.color, $event)"
></div>
</div>
JavaScript
const vm = new Vue({
el: '#app',
data: {
product: 'Socks',
image: './assets/socks-green.jpg',
imageTitle: 'Green Socks',
variants: [
{id: 1, color: 'green'},
{id: 2, color: 'blue'}
],
inventory: 3,
cart: 0
},
methods: {
addToCart() {
this.cart++;
this.inventory--;
},
setImage(color, event) {
console.log('Vue instance with data and methods', this, event);
this.image = `./assets/socks-${color}.jpg`;
}
}
});
console.log('Modify data directly from the console with `vm.product = "Shoes";`');
Templates
Displaying values:
<h1>{{ name.toUpperCase() }}</h1>
<p v-text="name"></p>
<p v-html="rawHtml"></p>
Computed Properties
Prefer computed
properties over doing string formatting in the templates directly
as they will only be re-evaluated when a relevant dependency changes.
Usage computed property: <h1>{{ title }}</h1>
:
new Vue({
data: {name: 'Vuer'},
computed: {
title() {
return this.name.toUpperCase();
}
}
});
Attributes
Binding to attribute with v-bind
directive:
<img v-bind:src="val">
<img v-bind:[evaluated]="val">
<img :src="val">
Evaluated should return a bindable property or null
.
Shorthands: :src
, :alt
, :title
, :class
, :style
, :disabled
:style
:style="{fontSize: '13px'}"
:style="{'font-size': styles.size}"
:style="styleObject"
:style="[obj1, obj2]"
- Use camelCase or kebab-case (quoted)
- Vendor prefixes are added automatically
- Can combine html
style
and Vue:style
attributes.
:class
:class="'btn' + btn.type"
:class="[cond ? 'active' : '']"
:class="{active: cond}"
Conditions
<!-- Falsy values will not render -->
<p v-if="inventory > 10">In Stock</p>
<p v-else-if="inventory">Almost sold out</p>
<p v-else>Out of Stock</p>
<!-- Render a block of multiple elements -->
<!-- The template tag itself will not be rendered -->
<template v-if="cond">
<h1>Title</h1>
<div>...</div>
</template>
<!-- Falsy values render with display: none -->
<!-- Less DOM changes === better performance -->
<!-- There is no v-else-show and it does not work with templates -->
<p v-show="inventory">In Stock</p>
v-show
- Use when you need to toggle something often
- Always rendered: Costlier initial rendering
v-if
- Use when the condition is unlikely to change
- Rendered only when the condition is true
v-once
: Will never be updated after initial render.
Looping
<!-- Render 5 stars -->
<i v-for="i in 5" class="fa fa-star" :key="i"></i>
<!-- Loop through an Array -->
<li v-for="item in ['Gold', 'Silver', 'Bronze']" :key="item">{{ item }}</li>
<li v-for="(item, index) in [1, 2, 3]" :key="item">{{ index + ' ' + item }}</li>
<!-- Loop through an Object -->
<li v-for="(value, key) in {a: 1}" :key="key">{{ value }}</li>
<li v-for="(value, key, index) in {a: 1}" :key="key">{{ value }}</li>
Like v-if
you can use a template to render a block of multiple elements.
:key
attribute:
- Should be a primitive type
- Recommended unless you want to intentionally rely on the default behavior
- Required when using
v-for
with a custom component
Events
<button v-on:click="nr++"></button>
<form @submit="methodName"></form>
<input @keyup.enter="fn(arg1, arg2, ...)">
Event Modifiers
@keyup.enter
:.enter
is called a Key Modifier- Modifiers are stackable:
.mod1.mod2
@keyup.page-down="onPageDown"
.prevent
==event.preventDefault()
.stop
==event.stopPropagation()
: Shorthand~
..once
: Unregister after first trigger.self
: Trigger only when the element is the.target
@click.ctrl.exact
: Only trigger when ctrl is pressed without any other keys@click.capture
: An event on an inner element is handled here before the.target
. Shorthand!
..passive
: Corresponds toaddEventListener
’s passive option. Shorthand&
.
Components
Usage
// Html
// <product :premium="premium" @add-to-cart="updateCart" class="main-prod"></product>
// JavaScript
new Vue({
data: {
premium: true,
cart: [],
},
methods: {
updateCart(product) {
this.cart.push(product);
}
}
});
Declare
const comp = Vue.component('product', {
// Props with validation
props: {
premium: {type: Boolean, required: true, default: false},
// Types: String, Number, Array, Object, Function, Promise, any ctor
// Null and undefined always pass validation
// oneOf: [String, Number],
// special: {
// default() { return 'calculatedValue'; },
// validator(value) { return true; }
// }
},
// Props without validation
// props: ['premium'],
template: `
<!-- Must have single root element -->
<div class="product">
<h1>{{ product }}</h1>
<p v-if="premium"><b>FREE Shipping</b></p>
<button @click="addToCart">
Add to Cart
</button>
</div>
`,
data() {
// data is a function inside a Vue.component()
// Do return a new object reference each time.
return {
product: 'Socks',
inventory: 3,
};
},
methods: {
addToCart() {
this.inventory--;
this.$emit('add-to-cart', {product: this.product});
}
},
// Local Subcomponent Registration
// const LocalComponent = {};
// components: {
// 'local-component': LocalComponent,
// }
// Watches
watch: {
iventory(newInventory, oldInventory) {
// TODO: Call backend to check if product is still available
}
},
// LifeCycle Methods
created() {
console.log('Http requests probably here');
},
mounted() {},
updated() {},
destroyed() {},
beforeDestroy() {},
});
Slots
Slots, like React props.children
or Angular ng-content
.
Usage
<alert-box>
<!-- Named Slot -->
<template v-slot:title>
<!-- Shorthand #title -->
Oh noes!
</template>
<!-- Default Slot -->
Something bad happened.
<!-- Could put default slot into: v-slot:default -->
</alert-box>
Declare
Vue.component('alert-box', {
template: `
<div>
<slot name="title">Error!</slot>
<slot>Default value</slot>
</div>
`
});
Forms
Html
<form @submit.prevent="onSubmit">
<input v-model="name" @keydown.ctrl.v.prevent="blockPaste">
<div v-for="error in errors" :key="error">
* {{ error }}
</div>
<select v-model.number="rating">
<!-- Other modifiers: -->
<!-- .lazy => Sync on change event -->
<!-- .trim => Strip whitespace -->
<option disabled value="">select</option>
<option v-for="i in 5" :key="i">{{ i }}</option>
</select>
<br>
<input
type="checkbox"
v-model="acceptTerms"
true-value="yes"
false-value="no"
>
Accept terms
<button>Submit</button>
</form>
JavaScript
new Vue({
data: {
name: null,
rating: '',
acceptTerms: false,
errors: []
},
methods: {
onSubmit() {
this.errors = [];
if (!this.rating) {
this.errors.push('Please select a rating');
return;
}
const review = {name: this.name, rating: this.rating};
console.log('Submitting', review);
},
blockPaste() {
console.log('Control + V is not allowed!');
}
},
});
Validation not included
vuelidate/vuelidate : Simple, lightweight model-based validation for Vue.js
baianat/vee-validate : Template Based Validation Framework for Vue.js
Caveat
Reuse of <input>
elements could lead to strange behavior.
<template v-if="loginType === 'username'">
<!-- Using `key` so the inputs are not reused when loginType changes -->
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<input placeholder="Enter your email address" key="email-input">
</template>
Custom Model Binding
Vue.component('custom-input', {
props: ['value'],
template: `<input :value="value" @input="$emit('input', $event.target.value)">`
})
// Html
// <custom-input v-model="searchText"></custom-input>
new Vue({
el: '#app',
data: {
searchText: ''
},
});
Reactivity
Array
Vue.js has wrapped array functions so that they are reactive:
push
, pop
, shift
, unshift
, splice
, sort
, reverse
Assigning a new array will reuse DOM elements if possible on the rerender.
It will not detect this.items[0] = 'newVal'
. If you must, use:
Vue.set(vm.items, indexOfItem, newValue);
vm.items.splice(indexOfItem, 1, newValue);
vm.$set(vm.items, indexOfItem, newValue);
Objects
Assigning a new property to an object is not reactive. Assign it a default value on initialization.
If you must, use:
Vue.set(vm.userProfile, 'age', 27);
vm.$set(vm.userProfile, 'age', 27);
Closing Thoughts
I was really surprised by Vue’s simplicity and terse syntax due its many shorthands. Tooling in Visual Studio Code and Chrome is really good. The CLI was pretty amazing at setting up a full blown starter project.
So, I learned me some Vue.js for a technical session and am already set on using it for a few small projects.