Reactive Objects
Vue is a reactive web framework.
Vue allows us to declare reactive objects within the <script> section using the ref()
function. We’ll call reactive objects refs from here on out. These refs have a property named value
that can hold a value of any JavaScript type. We can set and get the data stored in the value
property similar to setting and getting the value of any JavaScript object. Think of a ref as a special wrapper for an ordinary variable you would normally declare with const
or let
.
We can use a ref’s value
property in computations in the <script> section’s code, or as the value for an element’s attribute in the <template> section, and in many other contexts (see below). What makes refs special is that when the value stored in a ref changes, Vue recomputes and possibly re-renders the portion of the DOM that corresponds to the instance of the component with two exceptions: 1) Vue won’t necessarily re-render child components unless they themselves have refs that have changed and 2) Vue won’t re-render elements in the component that are excluded from re-rendering using the v-memo
directive.
For example, in the code below, we declare a ref named count
using ref()
and initializes its value to 0. We also have a function named increment()
that adds 1 to the value of count
each time the function is called. Notice that when we modify the value of count we modify count.value
.
We then bind the count
ref to the contents of the <h1> element using mustache syntax: {{ count }}
. Note that we do not need to append .value
after then name of the ref
when used in a template. This is because Vue automatically unwraps refs
when used inside templates.
With these simple expressions, we’ve instructed Vue to monitor the value in count
and automatically re-renders the contents of the <h1>
element when the value of count
changes.
[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<h1>Count: {{ count }}</h1>
<button @click="increment">Increment</button>
</template>
[/code]
We can also write the button handler inline as shown below. When we do, we again can omit .value
when using the ref
.
Note also that we can put any valid JavaScript expression that evaluates to a value inside the mustache syntax like {{ count * 2 }}
.
[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<h1>Count: {{ count * 2 }}</h1>
<button @click="count++"</button>
</template>
[/code]
V-bind:
We may also want to bind a ref
to an element’s attribute so that the value of the attribute will automatically change when the value of the ref
changes. Vue does not allow the use of mustache syntax inside HTML attributes, but has provided the v-bind:
directive to declare a ref-to-attribute binding.
In the example below we have a ref
that holds the name of a CSS class. We bind the ref
to the class
attribute of an element so that when the value of the ref
changes (by pressing the button), the element’s class
attribute automatically changes and the element is re-rendered.
[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const fontColor = ref('green')
</script>
<template>
<h1 v-bind:class="fontColor">{{ fontColor }}</h1>
<button @click="fontColor = (fontColor === 'red') ? 'green' : 'red'">Toggle</button>
</template>
<style>
.red { color: red }
.green { color: green }
</style>
[/code]
Note that v-bind does not create a 2-way binding. If the value of the ref changes then the value of the attribute changes, but if the value of the attribute changes and not as a result of the ref changing, then the value in the ref does not change.
For example, if I add another button and in the handler for the button I clear the classList
for the <h1>
element, then the <h1>
element will not have ‘red’ or ‘green’ in its class list, but the value of the fontColor
ref will still be either ‘red’ or green’. v-bind only establishes a one-way binding.
V-bind Shorthand
The shorthand syntax for v-bind
is simply to preface the attribute with a colon (:
) and omit v-bind
. For example, we can write the above h1
element as follows:
[code language=”JavaScript”]
<h1 :class="fontColor">{{ fontColor }}</h1>
[/code]
V-bind with Inline Style Properties
We can also use v-bind on a style property. In the example below we bind a ref (fontColor) to the value of an inline style property (color).
[code language=”JavaScript”]
<h1 :style="{ color: fontColor }">Binding style attr</h1>
[/code]
We can also bind a ref to multiple style properties using an object. In the example below we pass an object to ref()
and bind the object returned by ref()
to the <h1>
element’s style properties defined in the object.
[code language=”JavaScript”]
<script>
const styleObject = ref({
color: 'orange',
fontSize: '30px'
})
</script>
<template>
<h1 :style="styleObject">Binding style – reactive</h1>
<button @click="styleObject.color = 'blue'">Blue</button>
<button @click="styleObject.color = 'red'">Red</button>
</template>
[/code]
V-bind with Boolean Attributes
If a ref is bound to a Boolean attribute then the attribute will exist on the element if the ref has a truthy value or is equal to the empty string. Otherwise, the attribute will not be included on the element.
In the example below the button will have a disabled
attribute if the value of the count ref is even; otherwise the button will not have a disabled
attribute.
[code language=”JavaScript”]
<h1>Count is {{ count % 2 === 0 ? "even" : "odd" }}</h1>
<button :disabled="count % 2 === 0">Disabled if count is even</button>
[/code]
V-model
We know that v-bind only establishes a one-way binding between a ref and DOM elements on which the ref is used. To illustrate this point again, if we have an input
element and bind its value
attribute to a ref then:
- when the value of the ref changes (press button), the contents of the input changes because Vue re-renders the component, but
- if we enter text in the input element then the value of the ref does not change.
[code language=”JavaScript”]
<script>
const title = ref('Nightmare on Elm Street')
</script>
<template>
<input type="text" :value="title" v-memo="[title]"> <br>
<span>Ref: {{ title }}</span> <br>
<button @click="title += String.fromCodePoint(0x1f52a)">Append to ref</button>
</template>
[/code]
The use of v-memo
in the <input>
element above informs Vue to only re-render this particular input element if the value in the title
ref changes. I add this so that we modify the other refs in this page, this <input> element won’t be re-rendered thus resetting the contents of the input to the value in the title
ref.
When working with forms
its often useful to bind a ref to the value
attribute of an input
element so that we can initialize the value in the input
and also make it so that whatever the user types text in the input
the text is also saved in the ref. We can accomplish this by adding an event handler for the input
event.
[code language=”JavaScript”]
<script>
const username = ref('freddy')
</script>
<template>
<span>ref: {{ username }}</span> <br>
<input type="text" :value="username" @input="e => username = e.target.value">
</template>
[/code]
This pattern is used often so Vue added the v-model
directive. The v-model directive binds a ref to the value
attribute of an input and binds the element’s value
property to the ref thereby creating a 2-way binding.
[code language=”JavaScript”]
<script>
const address = ref('123 Elm Street')
</script>
<template>
<span>ref: {{ address }}</span> <br>
<input type="text" v-model="address"> <br>
</template>
[/code]