[VueMastery] Vue 3 Essentials

Code
Lesson2

Why the Composition API

There’s been some confusion over the new Vue 3 composition API. By the end of this lesson it should be clear why the limitations of Vue 2 have led to its creation, and how it solves a few problems for us.

There are currently three limitations you may have run into when working with Vue 2:

  • As your components get larger readability gets difficult.
  • The current code reuse patterns all come with drawbacks.
  • Vue 2 has limited TypeScript support out of the box.

I will go into detail with the first two, so it’s apparent what problem the new API solves.

Large components can be hard to read & maintain.

To wrap our head around this problem lets think about a component that takes care of searching the products on our website.

The code for this component, using the standard Vue component syntax, is going to look like this:

What happens when we also want to add the ability to sort the search results to this component. Our code then looks like:

Not too bad, until we want to add search filters and pagination features to the same component. Our new features will have code fragments that we’d be splitting amongst all of our component options (components, props, data, computed, methods, and lifecycle methods). If we visualize this using colors (below) you can see how our feature code will get split up, making our component much more difficult to read and parse which code goes with which feature.

As you can imagine (with the image on the right), if we can keep our feature code together, our code is going to be more readable, and thus more maintainable. If we go back to our original example and group things together using the composition API, here’s what it’d look like:

To do this with the setup() method (as shown above), we need to use the new Vue 3 composition API. The code inside setup() is a new syntax that I’ll be teaching in later lessons. It’s worth noting that this new syntax is entirely optional, and the standard way of writing Vue components is still completely valid.

I know when I first saw this, I wondered, “Wait, does this mean I create a gigantic setup method, and put all my code in there? How can that be how this works?”

No, don’t worry, this isn’t what happens. When you organize components by features using the composition API, you’ll be grouping features into composition functions that get called from your setup method, like so:

Using this our components can now be organized by logical concerns (also known as “features”). However, this doesn’t mean that our user interface will be made up of fewer components. You’re still going to use good component design patterns to organize your applications:

Now that you’ve seen how the Component API allows you to make large components more readable and maintainable, we can move on to the second limitation of Vue 2.

There’s no perfect way to reuse logic between components.

When it comes to reusing code across components there are 3 good solutions to do this in Vue 2, but each has its limitations. Let’s walk through each with our example. First, there are Mixins.

The good

  • Mixins can be organized by feature.

The not so good

  • They are conflict-prone , and you can end up with property name conflicts.
  • Unclear relationships on how mixins interact, if they do.
  • Not easily reusable if I want to configure the Mixin to use across other components.

This last item leads us to take a look at Mixin Factories , which are functions that return a customized version of a Mixin.

As you can see above, Mixin Factories allow us to customize the Mixins by sending in a configuration. Now we can configure this code to use across multiple components.

The good

  • Easily reusable now that we can configure the code.
  • We have more explicit relationships of how our Mixins interact.

The not so good

  • Namespacing requires strong conventions and discipline.
  • We still have implicit property additions, meaning we have to look inside the Mixin to figure out what properties it exposes.
  • There’s no instance access at runtime, so Mixin factories can’t be dynamically generated.

Luckily, there’s one more solution that can often be the most useful, Scoped Slots :

The good

  • Addresses just about every downside of Mixins.

The not so good

  • Your configuration ends up in your template, which ideally should only contain what we want to render.
  • They increases indentation in your template, which can decrease readability.
  • Exposed properties are only available in the template.
  • Since we’re using 3 components instead of 1, it’s a bit less performant.

So as you can see, each solution has limitations. Vue 3’s composition API provides us a 4th way to extract reusable code, which might look something like this:

Now we’re creating components using the composition API inside functions that get imported and used in our setup method, where we have any configuration needed.

The good

  • We’re writing less code, so it’s easier to pull a feature from your component into a function.
  • It builds on your existing skills since you’re already familiar with functions.
  • It’s more flexible than Mixins and Scoped Slots since they’re just functions.
  • Intellisense, autocomplete, and typings already work in your code editor.

The not so good

  • Requires learning a new low-level API to define composition functions.
  • There are now two ways to write components instead of just the standard syntax.

Hopefully, the “why” behind the composition API is now clear to you, I know it wasn’t clear to me at first. In the next lesson I’ll be diving into the new syntax for composing components.

Setup & Reactive References

At this point, you’re probably wondering what the new composition API syntax looks like, and we’re about to dive in. First, we want to be clear about when you might use it, and then we’ll learn about the setup function and Reactive References or refs.

Disclaimer: If you haven’t caught on yet, the composition API is purely additive, and nothing is deprecated. You can code Vue 3 the same way you code up Vue 2.

When to use the Composition API?

When any of the following are true:

  • You want optimal TypeScript support.
  • The component is too large and needs to be organized by feature.
  • Need to reuse code across other components.
  • You & your team prefer the alternative syntax.

Disclaimer #2: The examples that follow are quite simple. Constructing components this simple using the Composition API would be unnecessary, but make it easier to learn.

Let’s start with a very simple component written using our normal Vue 2 API, which is valid in Vue 3.

<template>
  <div>Capacity: {{ capacity }}</div>
</template>
<script>
export default {
  data() {
    return {
      capacity: 3
    };
  }
};
</script>

Notice I have a simple capacity property, which is reactive. Vue knows to take each of the properties in the object returned by the data property, and make them reactive. This way, when these reactive properties get changed, components that use this property gets re-rendered. Duh, right? In the browser we see:

Using the Setup Function

In Vue 3 using the Composition API, I would start by writing that setup method we’ve seen before:

<template>
  <div>Capacity: {{ capacity }}</div>
</template>
<script>
export default {
  setup() {
    // more code to write
  }
};
</script>

The setup executes before any of the following options are evaluated:

  • Components
  • Props
  • Data
  • Methods
  • Computed Properties
  • Lifecycle methods

It’s also worth mentioning that the setup method does not have access “this”, unlike other Component options. In order to get access to the properties we normally would access using this, setup has two optional arguments. The first is props which is reactive and can be watched, as such:

import { watch } from "vue";
export default {
  props: {
    name: String
  },
  setup(props) {
    watch(() => {
      console.log(props.name);
    });
  }
};

The second argument is context, that has access to a bunch of useful data:

setup(props, context) {
  context.attrs;
  context.slots;
  context.parent;
  context.root;
  context.emit;
}

But let’s get back to our example. Our code needs a reactive reference:

Reactive References

<template>
 <div>Capacity: {{ capacity }}</div>
</template>
<script>
import { ref } from "vue";
export default {
  setup() {
    const capacity = ref(3);
    // additional code to write
  }
};
</script>

const capacity = ref(3) is creating a “Reactive Reference.” Basically it’s wrapping our primitive integer (3) in an object, which will allow us to track changes. Remember, previously our data() option already wrapping our primitive (capacity) inside an object.

Aside: The composition API allows us to declare reactive primitives that aren’t associated with a component, and this is how we do it.

One last step, we need to explicitly return an object with properties our template will need to render properly.

<template>
  <div>Capacity: {{ capacity }}</div>
</template>
<script>
import { ref } from "vue";
export default {
  setup() {
    const capacity = ref(3);
    return { capacity };
  }
};
</script>

This returned object is how we expose which data we need access to in the renderContext .

Being explicit like this is a little more verbose, but it’s also intentional. It helps with longer-term maintainability because we can control what gets exposed to the template, and trace where a template property is defined. We now have what we started with:

Using Vue 3 with Vue 2

It’s worth noting that you can use the Vue 3 Composition API with Vue 2 today by using this plugin. Once you install and configure it on a Vue 2 application, you’d use the same syntax I’m teaching you above with one small change. Instead of

import { ref } from "vue";

you would write

import { ref } from "@vue/composition-api";

That’s how I tested all the above code, in case you’re wondering.

Next, we’ll learn how to write component methods using this new syntax.