Reactivity

A Vue component without JavaScript consists of static HTML elements.  The structure of the page and the data displayed on the page is not intended to change.  When we add JavaScript to a component we encode actions that are performed when the user interacts with the visual component (the view).  These actions can then alter the values of variables that have been declared, which in turn can alter the structure of the component (the HTML), the appearance of the component (e.g. CSS changes), and the information that is displayed.  The variables that we declare in the JavaScript which determine how and what HTML is rendered are called state variables. The value of the set of state variables at any particular point in time is called the state of the component.  The State Management guide in the Vue documentation has a great diagram showing the circular relationship between the state, the view, and the actions.

You may have heard that Vue is a reactive framework.  What does this mean?

The Vue framework defines a particular syntax for specifying in a HTML template how a view should be rendered based on the values of the state variables.  This special syntax removes the need for developer to write JavaScript functions that manually modify the DOM when the value in a state variable changes.  Instead, Vue, behind the scenes, automatically and continuously, monitors the state of the component and if a state variable is modified, reacts by automatically modifying the part of the DOM that is dependent on the state variable.

Later in the course we’ll discuss how that state of one component can affect the state of another, but for now let’s investigate a single independent component.  We’ll show how to define a component’s state variables and how we can annotate the template to make the view dependent on state variables.

Defining and Using State Variables

We can write the JavaScript code for a component using either the Options API or the newer Composition API.   We’ll be using the Composition API.

To declare reactive state variables using Composition, since we are using a build process, we can specifying that we are using Composition by including the word setup in our opening script tag.

<script setup>
  ...
</script>

When we do this, all top-level imports, functions, and variables declared in the <script setup> are accessible within the component’s template.

If we are not using a build process we would have to export a setup() function (see the Vue documentation for more information).

Now inside the script we can either declare state variables using ref() or reactive().  Each has its benefits and limitations.  Let’s first look at ref().

Using ref() to Register State Variables

To use ref() we first need to import ref from ‘vue’.  The function ref() takes a value of any type as an argument, creates a ref object with a property named value and returns the ref object.  The ref object’s value property is reactive and monitored by Vue.

In the example below we create a constant named count that holds a ref object which hold the value 0.   We can then use the value property to retrieve and modify its value.

<script setup>
  import { ref } from 'vue'

  const count = ref(1)
  console.log(count.value)    // 1

  function increment() {
    count.value++
  }
</script>

Below we show how an exposed function (increment) and the ref (count) can be used in a template.  Note how easy it is to register a button handler in Vue and how we use mustache syntax to embed the value of a state variable.

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

Notice that in order to display the value held in the ref we don’t specify count.value but rather just specify the name of the ref.  This is because Vue automatically unwraps refs in templates if the top-level object referenced is a ref.

Just to go back to our original discussion.  You see in this example that the action (button press) modifies a reactive state variable (count) which is monitored by Vue and since the variable is used in the DOM, when its value is changed Vue automatically updates the DOM.  This is the power of a reactive framework.

As we noted earlier, we can use ref() on any type value including objects and arrays.

const address = ref({
  street: "123 Main Street",
  city: "Bridgewater",
  state: "VA"
})

When we use address in a template we can reference its properties individually.

<span>
  {{addr.street }} {{addr.city }}, {{addr.state }}
</span>
If we want to modify all of the properties of the ref in a handler we can assign the ref a new object.
function changeAll() {
  addr.value = {
    street: "456 Water Street",
    city: "Winthrop",
    state: "NY"
  }
}
Similarly if we want to modify a single property we can access the property through addr.value as shown below.
function changeState() {
  addr.value.state = "WI"
}
We can also create objects whose properties are refs and tracked by Vue.
const name = {
  first: ref("Jesse"),
  last: ref("Martinez")
}
When we specify the first and last name properties in a template we must include the value property since unwrapping does not occur when the top-level object (name) is not a ref.
<span>
  {{ name.first.value }}, {{ name.last.value }}
</span>
Let’s create a button handler that modifies the value of the first name.
function toggleName() {
  name.first.value = (name.first.value === "Jesse") ? "Mary" : "Jesse"
}
Now in the template we include a button and register the button handler.
<buttonclass="btn btn-primary" @click="toggleName"> Toggle </button>
Using reactive() to Register State Variables
To use the reactive() function we have to import the function from vue.
<script setup>
  import { reactive } from 'vue'
  ...
The reactive() function can take objects types such as objects, arrays and collection types.
As with ref(), reactive() returns a proxy object that is reactive.  The original object passed to reactive() is not reactive.  So it is best practice to exclusively use proxy objects to maintain your state variables.
const song = reactive({
  title: "Sunflower",
  artist: ["Post Malone", "Swae Lee"]
})
Any nested object of a proxy object is reactive.  So we can add properties to the proxy as shown below and the new property is reactive.
song.producer = ["Carter Lang", "Louis Bell"]
When properties of proxy object created with reactive() are passed to functions, their values are passed, not the proxy object, and if changed the state of the proxy will not change.  For example if we try to change the song title of the song by passing just the song title property to a function, the state of the title will not change in the proxy object.
function changeTitle(t) {
  t = "I Like You"
}

function updateTitle() {
  changeTitle(song.title) // does not change title in proxy
}

Further Reading

© 2023, Eric.