Chapter 1. Vue.js: The Basics
As explained in the preface, Vue.js is the library at the heart of an ecosystem that allows us to create powerful client-side applications. We don’t have to use the whole ecosystem just to build a website, though, so we’ll start by looking at Vue by itself.
Why Vue.js?
Without a framework, we’d end up with a mess of unmaintainable code, the vast majority of which would be dealing with stuff that the framework abstracts away from us. Take the following two code examples, both of which download a list of items from an Ajax resource and display them on the page. The first one is powered by jQuery, while the second one is using Vue.
Using jQuery, we download the items, select the ul
element, and then if there are items, we iterate through them, manually creating a list element, adding the is-blue
class if wanted, and setting the text to be the item. Finally, we append it to the ul
element:
<ul
class=
"js-items"
></ul>
<script>
$
(
function
()
{
$
.
get
(
'https://example.com/items.json'
)
.
then
(
function
(
data
)
{
var
$itemsUl
=
$
(
'.js-items'
);
if
(
!
data
.
items
.
length
)
{
var
$noItems
=
$
(
'li'
);
$noItems
.
text
(
'Sorry, there are no items.'
);
$itemsUl
.
append
(
$noItems
);
}
else
{
data
.
items
.
forEach
(
function
(
item
)
{
var
$newItem
=
$
(
'li'
);
$newItem
.
text
(
item
);
if
(
item
.
includes
(
'blue'
))
{
$newItem
.
addClass
(
'is-blue'
);
}
$itemsUl
.
append
(
$newItem
);
});
}
});
});
</script>
This is what the code does:
-
It makes an Ajax request using
$.get()
. -
It selects the element matching
.js-items
and stores it in the$itemsUl
object. -
If there are no items in the list downloaded, it creates an
li
element, sets the text of theli
element to indicate that there were no items, and adds it to the document.If there are items in the list, it iterates through them in a loop.
-
For every item in the list, it creates an
li
element and sets the text to be the item. Then, if the item contains the stringblue
, it sets the class of the element tois-blue
. Finally, it adds the element to the document.
Every step had to be done manually—every element created and appended to the document individually. We have to read all the way through the code to work out exactly what is going on, and it isn’t obvious at all at first glance.
With Vue, the code providing the same functionality is much simpler to read and understand—even if you’re not yet familiar with Vue:
<ul
class=
"js-items"
>
<li
v-if=
"!items.length"
>
Sorry, there are no items.</li>
<li
v-for=
"item in items"
:class=
"{ 'is-blue': item.includes('blue') }"
>
{{ item }}</li>
</ul>
<script>
new
Vue
({
el
:
'.js-items'
,
data
:
{
items
:
[]
},
created
()
{
fetch
(
'https://example.com/items.json'
)
.
then
((
res
)
=>
res
.
json
())
.
then
((
data
)
=>
{
this
.
items
=
data
.
items
;
});
}
});
</script>
This code does the following:
-
It makes an Ajax request using
fetch()
. -
It parses the response from JSON into a JavaScript object.
-
It stores the downloaded items in the
items
data property.
That’s all the actual logic in the code. Now that the items have been downloaded and stored, we can use Vue’s templating functionality to write the elements to the Document Object Model (DOM), which is how elements are represented on an HTML page. We tell Vue that we want one li
element for every item and that the value should be item
. Vue handles the creation of the elements and the setting of the class for us.
Don’t worry about fully understanding the code example if you don’t yet. I’ll slow down and introduce the various concepts one at a time throughout the book.
Not only is the second example significantly shorter, but it’s also a lot easier to read, as the actual logic of the app is completely separated from the view logic. Instead of having to wade through some jQuery to work out what is being added when, we can look at the template: if there are no items, a warning is displayed; otherwise, the items are displayed as list elements. The difference becomes even more noticeable with larger examples. Imagine we wanted to add a reload button to the page that would send a request to the server, get the new items, and update the page when the user clicks a button. With the Vue example, that’s only a couple of additional lines of code, but with the jQuery example, things start to get complicated.
In addition to the core Vue framework, several libraries work great with Vue and are maintained by the same people who maintain Vue itself. For routing—displaying different content depending on the URL of the application—there’s vue-router. For managing state—sharing data between components in one global store—there’s vuex, and for unit testing Vue components, there’s vue-test-utils. I cover all three of those libraries and give them a proper introduction later in the book: vue-router in Chapter 5, vuex in Chapter 6, and vue-test-utils in Chapter 7.
Installation and Setup
You don’t need any special tools to install Vue. The following will do just fine:
<div
id=
"app"
></div>
<script
src=
"https://unpkg.com/vue"
></script>
<script>
new
Vue
({
el
:
'#app'
,
created
()
{
// This code will run on startup
}
});
</script>
This example contains three important things. First, there is a div
with the ID app
, which is where we’re going to initiate Vue onto—for various reasons, we can’t initiate it onto the body element itself. Then, we’re downloading the CDN1 version of Vue onto our page. You can also use a local copy of Vue, but for the sake of simplicity, we’ll go with this for now. Finally, we’re running some JavaScript of our own, which creates a new instance of Vue
with the el
property set pointing at the div
previously mentioned.
That works great on simple pages, but with anything more complicated, you probably want to use a bundler such as webpack. Among other things, this will allow you to write your JavaScript using ECMAScript 2015 (and above), write one component per file and import components into other components, and write CSS scoped to a specific component (covered in more detail in Chapter 2).
vue-loader and webpack
vue-loader is a loader for webpack that allows you to write all the HTML, JavaScript, and CSS for a component in a single file. We’ll be exploring it properly in Chapter 2, but for now all you need to know is how to install it. If you have an existing webpack setup or favorite webpack template, you can install it by installing vue-loader through npm, and then adding the following to your webpack loader configuration:
module
:
{
loaders
:
[
{
test
:
/\.vue$/
,
loader
:
'vue'
,
},
// ... your other loaders ...
]
}
If you don’t already have a webpack setup or you’re struggling with adding vue-loader, don’t worry! I’ve never managed to set up webpack from scratch either. There is a template you can use to set up a vue project using webpack that already has vue-loader installed. You can use it through vue-cli:
$ npm install --global vue-cli $ vue init webpack
You’ll then be asked a series of questions, such as what the project should be called and what dependencies it requires, and once you’ve answered them, vue-cli will set up a basic project for you:
Try this now, and then follow the instruction it outputs to start the server.
Congratulations—you’ve just set up your first Vue project!
You can then explore the generated files to see what’s going on. Most of the important stuff is happening in the src directory in the .vue files.
Templates, Data, and Directives
At the heart of Vue is a way to display data on the page. This is done using templates. Normal HTML is embellished using special attributes—known as directives—that we use to tell Vue what we want to happen and what it should do with the data we’ve provided it.
Let’s jump straight into an example. The following example will display “Good morning!” in the morning, “Good afternoon!” until 6 p.m., and “Good evening!” after that:
<div
id=
"app"
>
<p
v-if=
"isMorning"
>
Good morning!</p>
<p
v-if=
"isAfternoon"
>
Good afternoon!</p>
<p
v-if=
"isEvening"
>
Good evening!</p>
</div>
<script>
var
hours
=
new
Date
().
getHours
();
new
Vue
({
el
:
'#app'
,
data
:
{
isMorning
:
hours
<
12
,
isAfternoon
:
hours
>=
12
&&
hours
<
18
,
isEvening
:
hours
>=
18
}
});
</script>
Let’s talk about the last bit, first: the data object. This is how we tell Vue what data we want to display in the template. We’ve set three properties of the object—isMorning
, isAfternoon
, and isEvening
—one of which is true, and two of which are false, depending what time of day it is.
Then, in the template, we’re using the v-if
directive to show only one of the three greetings, depending on what the variable is set to. The element that v-if
is set on is displayed only if the value passed to it is truthy; otherwise, the element is not written to the page. If the time is 2:30 p.m., the following is output to the page:
<div
id=
"app"
>
<p>
Good afternoon!</p>
</div>
Note
Although Vue has reactive functionality, the preceding example is not reactive, and the page will not update when the time changes. We’ll cover reactivity in more detail later.
Quite a bit of duplication occurs in the previous example, though: it would be better if we could set the time as a data variable and then do the comparison logic in the template. Luckily, we can! Vue evaluates simple expressions inside v-if
:
<div
id=
"app"
>
<p
v-if=
"hours < 12"
>
Good morning!</p>
<p
v-if=
"hours >= 12 && hours < 18"
>
Good afternoon!</p>
<p
v-if=
"hours >= 18"
>
Good evening!</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
hours
:
new
Date
().
getHours
()
}
});
</script>
Writing code in this manner, with the business logic in the JavaScript and the view logic in the template, means that we can tell at a glance exactly what will be displayed when on the page. This is a much better approach than having the code responsible for deciding whether to show or hide the element in some JavaScript far away from the element in question.
Later, we’ll be looking at computed properties, which we can use to make the preceding code a lot cleaner—it’s a bit repetitive.
In addition to using directives, we can also pass data into templates by using interpolation, as follows:
<div
id=
"app"
>
<p>
Hello, {{ greetee }}!</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
greetee
:
'world'
}
});
</script>
This outputs the following to the page:
<div
id=
"app"
>
<p>
Hello, world!</p>
</div>
We can also combine the two, using both directives and interpolation to show some text only if it is defined or useful. See if you can figure out what the following code displays on the page and when:
<div
id=
"app"
>
<p
v-if=
"path === '/'"
>
You are on the home page</p>
<p
v-else
>
You're on {{ path }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
path
:
location
.
pathname
}
});
</script>
location.pathname
is the path in the URL of the page, so it can be “/” when on the root of the site, or “/post/1635” when somewhere else on the site. The preceding code tells you whether you’re on the home page of the site: in a v-if
directive, it tests whether the path is equal to “/” (and the user is therefore on the root page of the site), and then we’re introduced to a new directive, v-else
. It’s pretty simple: when used after an element with v-if
, it works like the else
statement of an if-else statement. The second element is displayed on the page only when the first element is not.
In addition to being able to pass through strings and numbers, as you’ve seen, it’s also possible to pass other types of data into templates. Because we can execute simple expressions in the templates, we can pass an array or object into the template and look up a single property or item:
<div
id=
"app"
>
<p>
The second dog is {{ dogs[1] }}</p>
<p>
All the dogs are {{ dogs }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
dogs
:
[
'Rex'
,
'Rover'
,
'Henrietta'
,
'Alan'
]
}
});
</script>
The following is output to the page:
The second dog is Rover All the dogs are [ "Rex", "Rover", "henrietta", "Alan" ]
As you can see, if you output a whole array or object to the page, Vue outputs the JSON-encoded value to the page. This can be useful when debugging instead of logging to console, as the value displayed on the page will update whenever the value changes.
v-if Versus v-show
You met v-if
in the previous section to show and hide an element, but how does it do that, and what’s the difference between it and the similar sounding v-show
?
If the value of a v-if
directive evaluates to falsy,2 the element isn’t output to the DOM.
The following Vue template
<div
v-if=
"true"
>
one</div>
<div
v-if=
"false"
>
two</div>
results in the following output:
<div>
one</div>
Compare that to v-show
, which uses CSS to show and hide the element.
The following Vue template
<div
v-show=
"true"
>
one</div>
<div
v-show=
"false"
>
two</div>
results in the following output:
<div>
one</div>
<div
style=
"display: none"
>
one</div>
Your users will (probably) see the same thing, but other implications and differences exist between the two.
First, because the inside element hidden using v-if
isn’t going to be displayed, Vue doesn’t try to generate the HTML; this isn’t the case with the v-show
example. This means that v-if
is better for hiding stuff that hasn’t loaded yet.
In this example, an error will be thrown:
<div
id=
"app"
>
<div
v-show=
"user"
>
<p>
User name: {{ user.name }}</p>
</div>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
user
:
undefined
}
});
</script>
The reason the error will be thrown is that Vue has attempted to execute user.name
—a property of an object that doesn’t exist. The same example using v-if
works just fine, because Vue doesn’t attempt to generate the inside of the element until the v-if
statement is truthy.
Also, two conditionals are related to v-if
: v-else-if
and v-else
. They behave pretty much as you’d expect:
<div
v-if=
"state === 'loading'"
>
Loading…</div>
<div
v-else-if=
"state === 'error'"
>
An error occurred</div>
<div
v-else
>
…our content!</div>
The first div
displays if state
is loading
, the second if state
is error
, and the third if state
is anything else. Only one of the elements will display at a time.
OK, so having seen that, why would anyone want to use v-show
?
Using v-if
has a performance cost. Every time an element is added or removed, work has to be done to generate the DOM tree underneath it, which can sometimes be a lot of stuff. v-show
has no such cost beyond the initial setup cost. If you’re expecting something to change frequently, v-show
might be the best choice.
Also, if the element contains any images, then hiding the parent with only CSS allows the browser to download the image ahead of it being displayed, meaning that it can be displayed as soon as v-show
becomes truthy. Otherwise, it wouldn’t start downloading until it was supposed to be displayed.
Looping in Templates
Another directive I find myself using a lot is the v-for
directive, which loops through the elements of an array or object, outputting the element multiple times. Take the following example:
<div
id=
"app"
>
<ul>
<li
v-for=
"dog in dogs"
>
{{ dog }}</li>
</ul>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
dogs
:
[
'Rex'
,
'Rover'
,
'Henrietta'
,
'Alan'
]
}
});
</script>
That outputs every item of the array to the page in list elements, like this:
<div
id=
"app"
>
<ul>
<li>
Rex</li>
<li>
Rover</li>
<li>
Henrietta</li>
<li>
Alan</li>
</ul>
</div>
The v-for
directive also works with objects. Consider the following example, which takes an object containing the average rent of a few cities and outputs them to the page:
<div
id=
"app"
>
<ul>
<li
v-for=
"(rent, city) in averageRent"
>
The average rent in {{ city }} is ${{ rent }}</li>
</ul>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
averageRent
:
{
london
:
1650
,
paris
:
1730
,
NYC
:
3680
}
}
});
</script>
The syntax here is slightly different, because we want to get the key as well—having just the rent on the page but not the names of cities wouldn’t be too useful. However, if we don’t want the keys, we can use the same syntax as before, v-for="rent in averageRent"
. This also applies to arrays: if we want the index of an array, we can use the bracket and comma syntax that you just saw with the array: v-for="(dog, i) in dogs"
.
Note the order of the arguments: it’s (value, key)
, not the other way around.
Finally, if you just want a simple counter, you can pass a number in as the argument. The following outputs the numbers 1 to 10:
<div
id=
"app"
>
<ul>
<li
v-for=
"n in 10"
>
{{ n }}</li>
</ul>
</div>
<script>
new
Vue
({
el
:
'#app'
});
</script>
You might have expected the numbers 0 to 9 to be output, but that isn’t the case. To start the list at 0, it’s usually easier to refer to n - 1
instead of just n
.
Binding Arguments
Some directives, such as v-bind
, take arguments. The v-bind
directive is used to bind a value to an HTML attribute. For instance, the following example binds the value submit
to the button type:
<div
id=
"app"
>
<button
v-bind:type=
"buttonType"
>
Test button</button>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
buttonType
:
'submit'
}
});
</script>
The following is output to the document:
<button
type=
"submit"
>
Test button</button>
v-bind
is the name of the directive, and type
is the argument: in this case, the name of the attribute we want to bind the given variable to. buttonType
is the value.
This also works with properties, such as disabled
and checked
: if the expression passed is truthy, the output element will have the property, and if it is falsy, the element will not:
<div
id=
"app"
>
<button
v-bind:disabled=
"buttonDisabled"
>
Test button</button>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
buttonDisabled
:
true
}
});
</script>
When using v-bind
with a lot of attributes, it can be pretty repetitive to write it out multiple times. There’s a shorter way to write it: you can omit the v-bind
part of the directive and use a colon. For example, this is how you would rewrite the preceding code example using the shorter syntax:
<div
id=
"app"
>
<button
:disabled=
"buttonDisabled"
>
Test button</button>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
buttonDisabled
:
true
}
});
</script>
While your choice of syntax is obviously a personal preference, I much prefer the shorter syntax and rarely write v-bind
in my code.
Reactivity
The last few sections of this chapter have shown how we can use Vue to output HTML to the DOM from values in JavaScript—but so what? What makes Vue different from any templating language?
In addition to creating the HTML in the first place, Vue watches the data
object for changes and updates the DOM when the data changes. To demonstrate this, let’s make a simple timer app that tells you how long the page has been open. We’ll need only one variable, which we’ll call seconds
, and we’ll use setInterval
to increment that variable once a second:
<div
id=
"app"
>
<p>
{{ seconds }} seconds have elapsed since you opened the page.</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
seconds
:
0
},
created
()
{
setInterval
(()
=>
{
this
.
seconds
++
;
},
1000
);
}
});
</script>
The create
function runs when the app is initiated. We’ll cover the Vue life-cycle hooks in more detail later, so don’t worry about the function too much for now. this.seconds
in that function refers directly to the value in the data object, and manipulating it updates the template.
Here’s that example after the page has been open for a bit:
14 seconds have elapsed since you opened the page.
In addition to working with interpolation to output the value to the page, Vue’s reactivity functionality also works when using a property of the data object as an attribute to a directive. For example, if in the v-bind
example we ran this.buttonDisabled = !this.buttonDisabled;
once a second, we would see that the button disabled
property would be toggled once a second: the button would be enabled for a second, and then disabled for another.
Reactivity is an extremely important feature and part of what makes Vue so powerful. You’ll be seeing it a lot both in this book and in any Vue projects you work on.
How It Works
This section is a bit more advanced; feel free to skip it and come back to it later if you don’t understand everything.
Vue’s reactivity works by modifying every object added to the data object so that Vue is notified when it changes. Every property in an object is replaced with a getter and setter so that you can use the object as a normal object, but when you change the property, Vue knows that it has changed.
Take the following object:
const
data
=
{
userId
:
10
};
When userId
is changed, how do you know it has been changed? You could store a copy of the object and compare them to each other, but that’s not the most efficient way of doing it. That’s called dirty checking, and it’s the way Angular 1 did it.
Instead, you can override the property by using Object.defineProperty()
:
const
storedData
=
{};
storedData
.
userId
=
data
.
userId
;
Object
.
defineProperty
(
data
,
'userId'
,
{
get
()
{
return
storedData
.
userId
;
},
set
(
value
)
{
console
.
log
(
'User ID changed!'
);
storedData
.
userId
=
value
;
},
configurable
:
true
,
enumerable
:
true
};
This isn’t exactly how Vue does it, but it’s a good way to think about it.
Vue also wraps some array methods such as .splice()
on observed arrays with a proxy method to observe when the method is called. This is so that when you call .splice()
, Vue knows that you’ve updated the array, and any necessary view updates can be triggered.
For more information on how reactivity works in Vue, check out the “Reactivity in Depth” section of the official documentation.
Caveats
There are some limitations to how this works. Understanding how Vue’s reactivity works can help you know the caveats, or you could just memorize the list. Only a few caveats exist, so it’s not too tricky to memorize them.
Adding new properties to an object
Because the getter/setter functions are added when the instance is initialized, only existing properties are reactive; when you add a new property, it won’t be reactive if you do it directly:
const
vm
=
new
Vue
({
data
:
{
formData
:
{
username
:
'someuser'
}
}
});
vm
.
formData
.
name
=
'Some User'
;
While the formData.username
property will be reactive and respond to changes, the formData.name
property will not. There are a few ways around this.
The easiest way is to define the property on the object on initialization, but with a value of undefined
. The formData
object in the previous example would become this:
formData
:
{
username
:
'someuser'
,
name
:
undefined
}
Alternatively—and this is most useful if you’re updating multiple properties at the same time—you can use Object.assign()
to create a new object and override the only object:
vm
.
formData
=
Object
.
assign
({},
vm
.
formData
,
{
name
:
'Some User'
});
Finally, Vue provides a function called Vue.set()
that you can use to set reactive properties:
Vue
.
set
(
vm
.
formData
,
'name'
,
'Some User'
);
When inside a component, this function is also available as this.$set
.
Setting items on an array
You can’t directly set items on an array by using the index. The following will not work:
const
vm
=
new
Vue
({
data
:
{
dogs
:
[
'Rex'
,
'Rover'
,
'Henrietta'
,
'Alan'
]
}
});
vm
.
dogs
[
2
]
=
'Bob'
There are two ways you can do this instead. You can use .splice()
to remove the old item and add a new one:
vm
.
dogs
.
splice
(
2
,
1
,
'
Bob
);
Or you can use Vue.set()
again:
Vue
.
set
(
vm
.
dogs
,
2
,
'Bob'
);
Either way works just as well.
Setting the length of an array
In JavaScript, you can set the length of an array to either pad it to that length with empty items, or to cut off the end of the array (depending on whether the set length is more or less than the old length). You can’t do that with an array in the data object, because Vue won’t be able to detect any changes to the array.
You can use splice
instead:
vm
.
dogs
.
splice
(
newLength
);
This works only to shorten the array, not to make it longer.
Two-Way Data Binding
So far you’ve seen how we can use our data to write to the template and how Vue’s reactivity functionality means that when the data is updated, so is the template. That’s only one-way data binding, though. If you tried the following, the inputText
would stay the same, and the text below the input element would stay the same:
<div
id=
"app"
>
<input
type=
"text"
v-bind:value=
"inputText"
>
<p>
inputText: {{ inputText }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
inputText
:
'initial value'
}
});
</script>
To get this example to work as expected, you can’t use v-bind:value
—using v-bind
will update the value of the input only when the inputText
changes, and not vice versa. Instead, you can use the v-model
directive, which works on input elements to bind the value of the input to the corresponding property of the data object so that in addition to the input receiving the initial value of the data, when the input is updated, the data is updated too. Replacing the HTML part of the previous example with the following will now update the inputText: initial value
text when you change the value of the input field:
<div
id=
"app"
>
<input
type=
"text"
v-model=
"inputText"
>
<p>
inputText: {{ inputText }}</p>
</div>
It’s important to note that when you use v-model
, if you set the value
, checked
, or selected
attributes, they will be ignored. If you want to set the initial value of the input, set it in the data object instead. See the following example:
<div
id=
"app"
>
<input
type=
"text"
v-bind:value=
"inputText"
value=
"initial value"
>
<p>
inputText: {{ inputText }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
inputText
:
''
}
});
</script>
The inputText
will still be bound to the inputText
variable, and any text you put in the input box will appear in the paragraph element below it. However, the input will have no initial value, as it was set using the value
attribute instead of in the data object, as shown in the first example.
Input elements, multiline text inputs (textareas
), select elements, and checkboxes all work basically as expected: the value of the input and the value in the data object are the same (with checkboxes, the value in the data object will be a Boolean value). With radio inputs, it’s slightly different, as there is more than one element with the same v-model
. The name
attribute will be ignored, and the value stored in the data will be equal to the value
attribute of the currently selected radio input:
<div
id=
"app"
>
<label><input
type=
"radio"
v-model=
"value"
value=
"one"
>
One</label>
<label><input
type=
"radio"
v-model=
"value"
value=
"two"
>
Two</label>
<label><input
type=
"radio"
v-model=
"value"
value=
"three"
>
Three</label>
<p>
The value is {{ value }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
value
:
'one'
}
});
</script>
When the first checkbox is selected, the value of value
is one
; when the second is selected, it is two
, and so on. Although you can still use the name
attribute, Vue will ignore it, and the radio buttons will act normally (with only one being checked at a time) without it.
Setting HTML Dynamically
Sometimes you might want to set the HTML of an element from an expression. Let’s say you’re calling an API that is returning some HTML which you need to display on the page. In an ideal world, the API would return a JSON object that you could pass to Vue and handle the template yourself, but—it happens. Vue has automatic HTML escaping built in, so if you try to write {{ yourHtml }}
, the HTML characters will be escaped—it’ll look like <strong>this</strong>
in the source—and it will appear as the HTML text sent down from the API on the page. Not ideal!
If you want to take HTML and display it on the page, you can use the v-html
directive as follows:
<div
v-html=
"yourHtml"
></div>
Then, whatever HTML is contained in yourHtml
will be written directly to the page without being escaped first.
Be careful with this! By writing HTML to the page from a variable, you are potentially opening yourself up to XSS vulnerabilities.3 Never put user input in v-html
or allow users to modify anything that goes through v-html
without carefully validating and escaping what they’ve written first. You could accidentally allow your users to execute malicious script tags on your site. Use v-html
only with data you trust.
Methods
This section explains how to use methods in Vue to make functions available in your templates in order to perform manipulations on your data.
Functions are pretty neat. They allow us to take a piece of logic and store it in a reusable way so that we can use it multiple times without repeating the code. It’s possible to use them in your Vue templates too, as methods. As shown in the following example, storing a function as a property of the methods
object makes it available in your templates:
<div
id=
"app"
>
<p>
Current status: {{ statusFromId(status) }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
status
:
2
},
methods
:
{
statusFromId
(
id
)
{
const
status
=
({
0
:
'Asleep'
,
1
:
'Eating'
,
2
:
'Learning Vue'
})[
id
];
return
status
||
'Unknown status: '
+
id
;
}
}
});
</script>
This turns a number representing the status—possibly supplied in another step by an API—into a human-readable string indicating the actual status. You can add methods by specifying them as properties of the methods
object.
In addition to being able to use methods in interpolations, you can use them in bound attributes—and really, anywhere that you can use JavaScript expressions. Here’s a short example:
<div
id=
"app"
>
<ul>
<li
v-for=
"number in filterPositive(numbers)"
>
{{ number }}</li>
</ul>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
numbers
:
[
-
5
,
0
,
2
,
-
1
,
1
,
0.5
]
},
methods
:
{
filterPositive
(
numbers
)
{
return
numbers
.
filter
((
number
)
=>
number
>=
0
);
}
}
});
</script>
The method takes an array and returns a new array with all the negative numbers removed, so in this example, we get a list of positive numbers output to the page.
We looked at reactivity previously, and you’ll be glad to know that it applies here too. If numbers
is changed—for example, if a number is added or removed—the method will be called again with the new numbers, and the information outputted to the page will be updated.
this
In a method, this
refers to the component that the method is attached to. You can access properties on the data object and other methods by using this
:
<div
id=
"app"
>
<p>
The sum of the positive numbers is {{ getPositiveNumbersSum() }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
numbers
:
[
-
5
,
0
,
2
,
-
1
,
1
,
0.5
]
},
methods
:
{
getPositiveNumbers
()
{
// Note that we're now using this.numbers
// to refer directly to the data object.
return
this
.
numbers
.
filter
((
number
)
=>
number
>=
0
);
},
getPositiveNumbersSum
()
{
return
this
.
getPositiveNumbers
().
reduce
((
sum
,
val
)
=>
sum
+
val
);
}
}
});
</script>
Here, this.numbers
in getPositiveNumbers
refers to the numbers array in the data object that we were previously passing in as an argument; this.getPositiveNumbers()
refers to the other method with that name.
You’ll see in future sections that you can access other things by using this
too.
Computed Properties
Computed properties sit halfway between properties of the data object and methods: you can access them as if they were properties of the data object, but they are specified as functions.
Check out the following example, which takes the data stored as numbers
, adds them all together, and outputs the total to the page:
<div
id=
"app"
>
<p>
Sum of numbers: {{ numberTotal }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
numbers
:
[
5
,
8
,
3
]
},
computed
:
{
numberTotal
()
{
return
numbers
.
reduce
((
sum
,
val
)
=>
sum
+
val
);
}
}
});
</script>
Although we could write the entire statement to calculate the total in the template, it’s much more convenient and readable to have it defined elsewhere. We can also access it in methods, other computed properties, and anywhere else in the component by using this
. As with methods, when numbers
changes, numberTotal
changes too, and the change is reflected in the template.
But what’s the difference between using computed properties and methods, aside from the obvious syntax difference? Well, there are a couple.
The first is that computed properties are cached: if you call a method multiple times in a template, the code inside the method will be run every single time the method is called, whereas if a computed property is called multiple times, the code inside will be run only once, and every time after that, the cached value will be used. The code will be run again only when a dependency of the method changes: for example, in the previous code sample, if we push a new item to basketItems
, the code inside basketTotal
is run again to get the new value. This is good if you’re doing something potentially resource intensive, as it ensures that the code will not be run more than necessary.
You can see this behavior by adding a console.log()
statement to a computed property that is repeatedly called. You can observe that the console.log()
statement, and thus the entire computed property, is evaluated only when the value changes:
<script>
new
Vue
({
el
:
'#app'
,
data
:
()
=>
({
value
:
10
,
}),
computed
:
{
doubleValue
()
{
console
.
log
(
'doubleValue computed property changed'
);
return
this
.
value
*
2
;
}
}
});
</script>
The string will be logged to the console only once when the app is initialized and once every time value
changes.
The other difference between computed properties and methods is that in addition to being able to get values of computed properties, as shown in the previous example, it’s possible to set values on computed properties and do something with the set value. You can do that by changing the computed property from a function to an object with get
and set
properties. For example, let’s say we want to add the ability to add new items to the numbers
array by adding or subtracting them from numberTotal
. We can do that by adding a setter that will compare the new value to the old value, and then append the difference to the numbers
array:
<div
id=
"app"
>
<p>
Sum of numbers: {{ numberTotal }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
numbers
:
[
5
,
8
,
3
]
},
computed
:
{
numberTotal
:
{
get
()
{
return
numbers
.
reduce
((
sum
,
val
)
=>
sum
+
val
);
},
set
(
newValue
)
{
const
oldValue
=
this
.
numberTotal
;
const
difference
=
newValue
-
oldValue
;
this
.
numbers
.
push
(
difference
).
}
}
}
});
</script>
Now, calling this.numberTotal += 5
somewhere else in the component will cause the number 5 to be added to the end of the numbers
array—neat!
Watchers
Watchers allow us to watch a property of the data object or a computed property for changes.
If you’re coming to Vue from another framework, you may have been wondering how to watch something for changes and waiting for this feature. Be careful, though! In Vue, there’s usually a better way to do something than to use a watcher—usually, using a computed property. For example, instead of setting data and then watching it for changes, using a computed property with a setter is a much better way to do it.
Watchers are easy to use: just specify the name of the property to watch. For example, to watch this.count
for changes:
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
count
:
0
},
watchers
:
{
count
()
{
// this.count has been changed!
}
}
});
</script>
Although most simple examples don’t require watchers, they are good for performing asynchronous operations. For example, let’s say we have an input that a user can type in, but we want to display on the page what was in the input five seconds ago. To do that, you can use v-model
to sync the value of the input to your data object, and then watch that value for changes and in the watcher write the value to another property of the data object after a delay:
<div
id=
"app"
>
<input
type=
"text"
v-model=
"inputValue"
>
<p>
Five seconds ago, the input said "{{ oldInputValue }}".</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
inputValue
:
''
,
oldInputValue
:
''
},
watch
:
{
inputValue
()
{
const
newValue
=
this
.
inputValue
;
setTimeout
(()
=>
{
this
.
oldInputValue
=
newValue
;
},
5000
);
}
}
});
</script>
Note that in this example, we’re writing this.inputValue
to a variable local to that function: this is because otherwise, when the function given to setTimeout
is called, this.inputValue
will have updated to the latest value!
Watching Properties of Objects in the Data Object
Sometimes you might have a whole object stored in your data object. To watch a property of that object for changes, you can specify the watcher name with dots in it, as if you were reading the object:
new
Vue
({
data
:
{
formData
:
{
username
:
''
}
},
watch
:
{
'formData.username'
()
{
// this.formData.username has changed
}
}
});
Getting the Old Value
Watchers are passed two arguments when their watched property is changed: the new value of the watched property, and the old value. This can be useful for seeing what has changed:
watch
:
{
inputValue
(
val
,
oldVal
)
{
console
.
log
(
val
,
oldVal
);
}
}
val
equals (in this case) this.inputValue
. I usually find myself using the latter instead.
Deep Watching
When watching an object, you might want to watch the entire object for changes, not just the property. By default, the watcher won’t fire if you’re watching formData
and you modify formData.username
; it will fire only if you replace the entire formData
property.
Watching the entire object is known as a deep watch, and we can tell Vue to do this by setting the deep
option to true
:
watch
:
{
formData
:
{
handler
()
{
console
.
log
(
val
,
oldVal
);
},
deep
:
true
}
}
Filters
Filters, also often seen in other templating languages, are a convenient way of manipulating data in your templates. I find them great for making simple display changes to strings and numbers: for example, changing a string to the correct case or displaying a number in a human-readable format.
Take the following code sample:
<div
id=
"app"
>
<p>
Product one cost: ${{ (productOneCost / 100).toFixed(2) }}</p>
<p>
Product two cost: ${{ (productTwoCost / 100).toFixed(2) }}</p>
<p>
Product three cost: ${{ (productThreeCost / 100).toFixed(2) }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
productOneCost
:
998
,
productTwoCost
:
2399
,
productThreeCost
:
5300
}
});
</script>
It works, but there’s a lot of duplication. For every item, we’re doing the math to convert it from cents to dollars, displaying it to two decimal places, and adding the dollar sign. Although we can definitely split that logic into a method, this time we’ll split that logic into a filter, as it’s more readable and can be added globally:
<div
id=
"app"
>
<p>
Product one cost: {{ productOneCost | formatCost }}</p>
<p>
Product two cost: {{ productTwoCost | formatCost }}</p>
<p>
Product three cost: {{ productThreeCost | formatCost }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
productOneCost
:
998
,
productTwoCost
:
2399
,
productThreeCost
:
5300
},
filters
:
{
formatCost
(
value
)
{
return
'$'
+
(
value
/
100
).
toFixed
(
2
);
}
}
});
</script>
That’s a lot better—much less duplication, much easier to read, and much more maintainable. Now if we decide we want to add logic to it—say, we want to add currency conversion functionality—we have to do it only once instead of changing the code every place it is used.
You can use multiple filters in the same expression by chaining them together. For example, if we have a round
filter that rounds a number to the nearest integer, you could use both filters together by writing {{ productOneCost | round | formatCost }}
. The round
filter would be called first, and then the value returned by that filter would be passed to the formatCost
filter and output to the page.
Filters can also take arguments. In the following example, the given string would be passed into the filter function as the second argument:
<div
id=
"app"
>
<p>
Product one cost: {{ productOneCost | formatCost('$') }}</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
productOneCost
:
998
,
},
filters
:
{
formatCost
(
value
,
symbol
)
{
return
symbol
+
(
value
/
100
).
toFixed
(
2
);
}
}
});
</script>
In addition to working with interpolation, you can use filters with v-bind
when binding values to arguments:
<div
id=
"app"
>
<input
type=
"text"
v-bind:value=
"productCost | formatCost('$')"
>
</div>
You can also use Vue.filter()
to register filters instead of on a per-component basis:
Vue
.
filter
(
'formatCost'
,
function
(
value
,
symbol
)
{
return
symbol
+
(
val
/
100
).
toFixed
(
2
);
});
This is good for registering filters that you’re going to use all over your app. I generally stick all of my filters in a separate file called filters.js.
Using filters carries two small caveats. The first is that filters are the only place where you can’t use this
to refer to methods and data on the components. This is intentional: it’s because filters are supposed to be pure functions, meaning that the function takes input and returns the same output every time without referring to any external data. If you want to access other data in a filter, pass it in as an argument.
The other caveat is that you can use filters only in interpolations and v-bind
directives. In Vue 1, it was possible to use them anywhere you could use expressions; for example, in v-for
directives:
<li
v-for=
"item in items | filterItems"
>
{{ item }}</li>
This is no longer the case in Vue 2, and you’ll have to use a method or computed property in the preceding case.
Accessing Elements Directly Using ref
Sometimes you might find yourself needing to access an element directly in the DOM; maybe you’re using a third-party library that isn’t written to work with Vue, or maybe you want to do something that Vue can’t quite handle itself. You can use ref
to access the element directly without having to use querySelector
or one of the other native ways to select an element from the DOM.
To access an element using a ref, set the ref
attribute of the element to a string that you can access the element using:
<canvas
ref=
"myCanvas"
></canvas>
Then in your JavaScript, the element will be stored on the this.$refs
object as whatever you set the ref
attribute to. In this case, you can access it by using this.$refs.myCanvas
.
Using ref
is especially useful in components. It’s possible that the same code could appear multiple times in the same page, meaning that you can’t add a unique class and use querySelector
at all. this.$refs
contains only the refs for the current component, meaning that if you call this.$refs.blablabla
in a component, it will always refer to the element in that component, not anywhere else in the document.
Inputs and Events
Until this point, pretty much everything you’ve seen has been about displaying data—we haven’t made anything that interactive yet. Now, I’ll introduce you to event binding in Vue.
To bind an event listener to an element, you can use the v-on
directive. It takes the name of the event as the argument, and the event listener as the passed value. For example, to increase the value of counter
by one when a button is clicked, you can write the following:
<button
v-on:click=
"counter++"
>
Click to increase counter</button>
<p>
You've clicked the button {{ counter }}</p>
times.
You can also provide the name of a method that will be called when the button is clicked:
<div
id=
"app"
>
<button
v-on:click=
"increase"
>
Click to increase counter</button>
<p>
You've clicked the button {{ counter }}</p>
times.</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
counter
:
0
},
methods
:
{
increase
(
e
)
{
this
.
counter
++
;
}
}
});
</script>
That works the same as the previous example.
One significant difference between using a method and putting the code inline is that if you use a method, the event object is passed in as the first argument. This event object is the native DOM event that you would get if you had added the event listener by using JavaScript’s built-in .addEventListener()
method and can be useful, for getting the keyCode
of a keyboard event, for example.
You can access the event when writing code inline, too, as the $event
variable. This can be useful when you’re adding the same event listener to multiple elements and want to know which one it was triggered from.
The v-on Shortcut
Similarly to the v-bind
directive, v-on
also has a shorter way of writing it. Instead of writing v-on:click
, you can write just @click
.
This is the same as the previous example:
<button
@
click=
"increase"
>
Click to increase counter</button>
I nearly always use the short version instead of writing v-on
.
Event Modifiers
You can also do a load of things to modify the event handler or the event itself.
To prevent the default action of the event from happening—for example, to stop a page navigation from happening when a link is clicked—you can use the .prevent
modifier:
<a
@
click
.
prevent=
"handleClick"
>
...</a>
To stop the event from propagating so that the event isn’t triggered on the parent elements, you can use the .stop
modifier:
<button
@
click
.
stop=
"handleClick"
>
...</button>
To have the event listener be triggered only the first time the event is fired, you can use the .once
modifier:
<button
@
click
.
once=
"handleFirstClick"
>
...</button>
To use capture mode, meaning that the event will be triggered on this element before it is dispatched on the elements below it in the tree (versus bubble mode, in which it’s fired on the element first and then bubbles up the DOM), you can use the .capture
modifier:
<div
@
click
.
capture=
"handleCapturedClick"
>
...</div>
To trigger the handler when the event was triggered on the element itself, not a child element (basically, when event.target
is the element the handler is being added to) you can use the .self
modifier:
<div
@
click
.
self=
"handleSelfClick"
>
...</div>
You can also specify just the event and modifier without giving it a value, and you can chain modifiers together. For example, the following will stop a click event from propagating any further down the tree—but only the first time:
<div
@
click
.
stop
.
capture
.
once
></div>
In addition to those event modifiers, there are also key
modifiers. These are used on keyboard events so that you can fire the event only when a certain key is pressed. Consider the following:
<div
id=
"app"
>
<form
@
keyup=
"handleKeyup"
>
...</form>
</div>
<script>
new
Vue
({
el
:
'#app'
,
methods
:
{
handleKeyup
(
e
)
{
if
(
e
.
keyCode
===
27
)
{
// do something
}
}
}
});
</script>
The code inside the if
statement will run only when the key with keyCode
27—the Escape key—is pressed. However, Vue has a way to do this built in as a modifier. You can specify the key code as the modifier, like so:
<form
@
keyup
.
27=
"handleEscape"
>
...</form>
Now, handleEscape
will be be triggered only when the Escape key is pressed. Aliases are also available for the most used keys: .enter
, .tab
, .delete
, .esc
, .space
, .up
, .down
, .left
, and .right
. Instead of writing @keyup.27
and having to remember which key 27 is, you can just write @keyup.esc
.
As of Vue 2.5.0, you can use any key name from the key
property of the event. For example, when you press the left Shift key, e.key
equals ShiftLeft
. To listen for when the Shift key is released, you can use the following:
<form
@
keyup
.
shift-left=
"handleShiftLeft"
>
...</form>
Similar to the key modifiers, three mouse button modifiers can be added to mouse events: .left
, .middle
, and .right
.
Some modifiers exist for modifier keys such as Ctrl and Shift: .ctrl
, .alt
, .shift
, and .meta
. The first three modifiers are fairly self-explanatory, but .meta
is less clear: on Windows, it’s the Windows key, and on macOS, it is the Command key.
Finally, an .exact
operator will trigger the event handler only if the specified keys and no other keys are pressed. For example:
<input
@
keydown
.
enter
.
exact=
"handleEnter"
>
That will fire when Enter is pressed, but not when any other keys—for example, Command-Enter or Ctrl-Enter—are pressed.
Life-Cycle Hooks
You’ve seen a couple of times now that if you specify a function as the created
property on a component or your Vue instance, it is called when the component is, well, created. This is an example of what’s known as a life-cycle hook, a series of functions that are called at various points throughout the life cycle of a component—all the way from when it created and added the DOM, to when it is destroyed.
Vue has eight life-cycle hooks, but they’re pretty easy to remember because four of them are just before hooks that are fired before the other ones.
Here’s the basic life cycle of an object: first, the Vue instance is initiated when new Vue()
is called. The first hook, beforeCreate
, is called, and reactivity is initiated. Then the created
hook is called—you can see how this works with the hook names. The “before” hook is called before the thing that triggers the hook happens, and then the actual hook is called afterward. Next, the template is compiled—either from the template
or render
options, or from the outerHTML
of the element that Vue was initialized to. The DOM element is now ready to be created, so the beforeMount
hook is fired, the element is created, and then the mounted
hook is fired.
One thing to be careful of is that as of Vue 2.0, the mounted
hook doesn’t guarantee that the element has been added to the DOM. To make sure that it has been added, you can call Vue.nextTick()
(also available as this.$nextTick()
) with a callback method containing the code you want to run after the element is definitely added to the DOM. For example:
<div
id=
"app"
>
<p>
Hello world</p>
</div>
<script>
new
Vue
({
el
:
'#app'
,
mounted
()
{
// Element might not have been added to the DOM yet
this
.
$nextTick
(()
=>
{
// Element has definitely been added to the DOM now
});
}
});
</script>
Four hooks have been fired so far, as the instance is initialized and then added to the DOM, and our users can now see our component. Maybe our data updates, though, so the DOM is updated to reflect that change. Before the change is made, another hook is fired, beforeUpdate
, and afterward, the updated
hook is fired. This hook can be fired multiple times as multiple changes to the DOM are made.
Finally, we’ve witnessed the creation and life of our component, but it’s time for it to go. Before it is removed from the DOM, the beforeDestroy
hook is fired, and after it has been removed, the destroyed
hook is fired.
Those are all the hooks that are fired throughout the life cycle of a Vue instance. Here they are again, but this time from the point of view of the hooks, not the instance:
-
beforeCreate
is fired before the instance is initialized. -
created
is fired after the instance has been initialized but before it is added to the DOM. -
beforeMount
is fired after the element is ready to be added to the DOM but before it has been. -
mounted
is fired after the element has been created (but not necessarily added to the DOM: usenextTick
for that). -
beforeUpdate
is fired when there are changes to be made to the DOM output. -
updated
is fired after changes have been written to the DOM. -
beforeDestroy
is fired when the component is about to be destroyed and removed from the DOM. -
destroyed
is fired after the component has been destroyed.
While there do seem to be a lot of hooks, you have to remember only four (created
, mounted
, updated
, and destroyed
), and you can work out the other four from there.
Custom Directives
In addition to the built-in directives such as v-if
, v-model
, and v-html
, it’s possible to create your own custom directives. Directives are great for times when you want to do something directly with the DOM—if you find that you’re not accessing the DOM, you’d probably be better off with a component instead.
As a simple example, let’s build a v-blink
directive, which simulates the behavior of the <blink>
tag. We’ll be able to use it like this:
<p
v-blink
>
This content will blink</p>
Adding a directive is similar to adding a filter: you can pass it in the directives
property of your Vue instance or component, or else you can register it globally using Vue.directive()
. You pass the name of the directive, plus an object containing hook functions that are run at various points through the life of the element the directive has been added to.
The five hook functions are bind
, inserted
, update
, componentUpdated
, and unbind
. I’ll explain them all in moment, but for now we’re just going to use the bind
hook, which is called when the directive is bound to the element. In the hook, we’ll toggle the visibility of the element once a second:
Vue
.
directive
(
'blink'
,
{
bind
(
el
)
{
let
isVisible
=
true
;
setInterval
(()
=>
{
isVisible
=
!
isVisible
;
el
.
style
.
visibility
=
isVisible
?
'visible'
:
'hidden'
;
},
1000
);
}
});
Now, any element the directive is applied to will blink once a second—just what we wanted.
Directives have multiple hook functions, just as the Vue instance and components have life-cycle hooks. They’re named differently and don’t do exactly the same thing as the life-cycle hooks, so let’s go through what they do now:
-
The
bind
hook is called when the directive is bound to the element. -
The
inserted
hook is called when the bound element has been inserted into its parent node—but just as withmounted
, this doesn’t guarantee that the element has been added to the document yet. Usethis.$nextTick
for that. -
The
update
hook is called when the parent of the component the directive is bound to is updated, but possibly before the component’s children have updated. -
The
componentUpdated
hook is similar to theupdate
hook, but is called after the component’s children have updated too. -
The
unbind
hook is used for teardown, and is called when the directive is unbound from the element.
You don’t have to call all the hooks every time. In fact, they’re all optional, so you don’t have to call any of them.
I find myself most commonly using the bind
and update
hooks. Conveniently, if you want to use just those two hooks, there’s a shorthand way of doing it—you can omit the object and specify a function that will be used as both hooks, as the argument:
Vue
.
directive
(
'my-directive'
,
(
el
)
=>
{
// This code will run both on "bind" and "update"
});
Hook Arguments
You’ve seen before that directives accept arguments (v-bind:class
), modifiers (v-on.once
), and values (v-if="expression"
). It’s possible to access all of these by using the second argument passed to the hook function, binding
.
If using v-my-directive:example.one.two="someExpression"
to call our directive, the binding
object will contain the following properties:
-
The
name
property is the name of the directive without thev-
. In this case, it’smy-directive
. -
The
value
property is the value passed to the directive. In this case, it would be whateversomeExpression
evaluates to. For example, if the data object were equal to{ someExpression: hello world }
,value
would equalhello world
. -
The
oldValue
property is the previous value passed to the directive. It’s available only inupdate
andcomponentUpdated
. With our example, if we had a different value forsomeExpression
, theupdate
hook would be called with the new value asvalue
and the old value asoldValue
. -
The
expression
property is the value of the attribute as a string before evaluation. In this case, it would literally besomeExpression
. -
The
arg
property is the argument passed to the directive; in this case,example
. -
The
modifiers
property is an object containing any modifiers passed to the directive. In this case, it will equal{ one: true, two: true }
.
To demonstrate how to use an argument, let’s modify our hook directive to take an argument indicating how quickly the element should blink:
Vue
.
directive
(
'blink'
,
{
bind
(
el
,
binding
)
{
let
isVisible
=
true
;
setInterval
(()
=>
{
isVisible
=
!
isVisible
;
el
.
style
.
visibility
=
isVisible
?
'visible'
:
'hidden'
;
},
binding
.
value
||
1000
);
}
});
I’ve added the binding
argument, and changed the timing on the setInterval
to binding.value || 1000
instead of just 1000
. This example is lacking any logic to update the component if the argument changes, though. In order to do that, we’d need to store the ID returned by setInterval
on a data attribute on the object and cancel the old interval before creating a new one in the update
hook.
Transitions and Animations
Vue contains a plethora of functionality that you can use to power animations and transitions in your Vue apps—from helping you with CSS transitions and JavaScript animations when elements enter or leave the page or are modified, to transitioning between components and even animating the data itself. There’s far too much to cover in a general book on Vue, so I’ll focus on the stuff I’ve found myself using most frequently, and you can check out the Vue documentation on transitions (with some really cool demos!) yourself later.
To start, let’s look at the <transition>
component and how to use it with CSS transitions to animate elements in and out of our document.
CSS Transitions
CSS transitions are good for simple animations, in which you’re just transitioning one or more CSS properties from one value to another. For example, you can transition color
from blue
to red
, or opacity
from 1
to 0
. Vue provides a <transition>
component that adds classes to a contained element with a v-if
directive, which you can use to apply CSS transitions when an element is added or removed.
Throughout this section, we’ll be referring to the following template, which has a button, and an element that toggles visibility when the button is pressed:
<div
id=
"app"
>
<button
@
click=
"divVisible = !divVisible"
>
Toggle visibility</button>
<div
v-if=
"divVisible"
>
This content is sometimes hidden</div>
</div>
<script>
new
Vue
({
el
:
'#app'
,
data
:
{
divVisible
:
true
}
});
</script>
Currently if you click the button, the content in the div
element will be immediately hidden and shown again, with no transition.
Let’s say we want to add a simple transition to it: we want it to fade in and out when the visibility is toggled. To do that, we can wrap the div
in a transition component, like so:
<transition
name=
"fade"
>
<div
v-if=
"divVisible"
>
This content is sometimes hidden</div>
</transition>
That by itself won’t do anything (it will behave exactly the way it did before we added the <transition>
element), but if we add the following CSS, we’ll get our fade transition:
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
.5s
;
}
.fade-enter
,
.fade-leave-to
{
opacity
:
0
;
}
Now, when you click the button to toggle visibility, the element fades in and out of the page instead of being added or removed instantly as it was before.
The way that this works is that Vue takes the name of the transition and uses it to add classes to the contained element at various points through the transition. Two types of transition, enter and leave, are applied when the element is added and removed from the document, respectively. The classes are as follows:
{name}-enter
-
This class is applied to the element as it is added to the DOM, and immediately removed after one frame. Use it to set CSS properties that you want to be removed as the element transitions in.
{name}-enter-active
-
This class is applied to the element for the entirety of the animation. It’s added at the same time the
-enter
class is added, and removed after the animation has completed. This class is good for setting the CSStransition
property, containing the length of the transition, the properties to transition, and the easings to use. {name}-enter-to
-
This class is added to the element at the same time as the
-enter
class is removed. It’s good for setting CSS properties that are added as the element transitions in, but I generally find it’s better to transition the inverse of the property on the-enter
class instead. {name}-leave
-
This class is the equivalent of the
-enter
class for the leave transitions. It’s added when the leave transition is triggered and then removed after one frame. Much like the-enter-to
class, this class isn’t that useful; it’s better to animate the inverse by using-leave-to
instead. {name}-leave-active
-
This class is the equivalent of
-enter-active
, but for the leave transitions. It’s applied for the entire duration of the exit transition. {name}-leave-to
-
This class is once again the equivalent of
-enter-to
, but for the leave transitions. It’s applied one frame after the transition starts (when-leave
is removed) and remains until the end of the transition.
In practice, I find myself using these four classes the most:
{name}-enter
-
This class sets CSS properties to transition during the enter transition.
{name}-enter-active
-
This class sets the
transition
CSS property for the enter transition. {name}-leave-active
-
This class sets the
transition
CSS property for the leave transition. {name}-leave-to
-
This class sets CSS properties to transition during the leave transition.
So the way the previous code example works is that both the enter and leave transitions are .5s
long, and opacity
is transitioned (with the default easing), and both on enter and leave we are transitioning to and from opacity: 0
. We’re fading the element in when it enters the document, and fading it out when it leaves again.
JavaScript Animations
In addition to CSS animations, the <transition>
component provides hooks that you can use to power JavaScript animations. Using these hooks, you can write animations by using either your own code or a library such as GreenSock or Velocity.
The hooks are similar to their counterparts for CSS transitions:
before-enter
-
This hook is fired before the enter animation starts and is good for setting initial values.
enter
-
This hook is fired when the enter animation starts and is where you can run the animation from. You can use a
done
callback to indicate when the animation has finished. afterEnter
-
This hook is fired when the enter animation has ended.
enterCancelled
-
This hook is fired when the enter animation is cancelled.
beforeLeave
-
This hook is the leave equivalent of
before-enter
, and is fired before the leave animation starts. leave
-
This hook is the leave equivalent of
enter
and is where you can run the animation from. afterLeave
-
This hook is fired when the leave animation has ended.
leaveCancelled
-
This hook is fired when the leave animation is cancelled.
These hooks are triggered on the <transition>
element as events, like so:
<transition
v-on:before-enter=
"handleBeforeEnter"
v-on:enter=
"handleEnter"
v-on:leave=
"handleLeave>
<div v-if="
divVisible
"
>
...</div>
</transition>
With those event handlers added, we can add the same effect as in the CSS transition example, but using GreenSock instead, like this:
new
Vue
({
el
:
'#app'
,
data
:
{
divVisible
:
false
},
methods
:
{
handleBeforeEnter
(
el
)
{
el
.
style
.
opacity
=
0
;
},
handleEnter
(
el
,
done
)
{
TweenLite
.
to
(
el
,
0.6
,
{
opacity
:
1
,
onComplete
:
done
});
},
handleLeave
(
el
,
done
)
{
TweenLite
.
to
(
el
,
0.6
,
{
opacity
:
0
,
onComplete
:
done
});
}
}
});
Using JavaScript animations, we can create much more complicated animations than we can with CSS transitions, including multiple steps, or a different transition every time. CSS transitions are generally more performant, though, so stick with them unless you need functionality you can’t get with just CSS transitions.
Now that we’ve explored some of the basic things that you can do with Vue, let’s look at how you can structure your code by using components.
Summary
In this chapter we looked at some of the basics of using Vue:
-
We looked at some of the reasons to use Vue.
-
We looked at how you can install and set up Vue using either a CDN or webpack.
-
We looked at the syntax of Vue: how you can use templates, the data object and directives to display your data on the page.
-
We looked at the difference between the
v-if
andv-show
directives. -
We looked at how you can use the
v-for
directive to loop in templates. -
We looked at how you can use
v-bind
to bind a property of the data object to an HTML attribute. -
We looked at how Vue automatically updates the value displayed on the page when the data updates: this is called reactivity.
-
We looked at two-way data binding: using
v-model
to both display data in an input and update the data object when the value of the input is changed. -
We looked at how you can use
v-html
to set the inner HTML of an element from the data object. -
We looked at how you can use methods to make functions available to your templates and throughout your Vue instance. We also looked at what
this
means inside a method. -
We looked at how you can use computed properties to create values that you can access as if they are properties of the data object, but that are computed at run-time and specified as functions.
-
We looked at how you can use watchers to watch properties of the data object or computed properties and do something when they change—but it’s generally a good idea to avoid watchers and use computed properties instead.
-
We looked at filters, a convinient way of manipulating data in your templates—useful for formatting data, for example.
-
We looked at how you can access elements directly using
ref
, which is useful if you’re using a third-party library that doesn’t work great with Vue, or if you want to do something that Vue itself can’t handle. -
We looked at event binding using
v-on
, or the short syntax using@
followed by the event name. -
We looked at the life-cycle of a Vue instance and how you can use hooks to execute code on them.
-
We looked at how you can create your own custom directives.
-
We looked at how Vue provides functionality to work with CSS transitions and JavaScript animations.
1 A content delivery network (CDN) is hosted on someone else’s servers all around the world for quick delivery. A CDN is useful for development and quick prototyping, but you should research whether it’s right for you before using unpkg
in production.
2 A falsy value is a value that is false
, undefined
, null
, 0
, ""
, or NaN
.
3 Cross-site scripting (XSS) allows other people to execute abitrary code on your website.
Get Vue.js: Up and Running now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.