Vue Composition API part 2: The setup script

Vue Composition API part 2: The setup script

6 Feb 2023 javascript vue

In part one we got to grips with the fundamentals of Vue's game-changing Composition API. In this part we'll look at a later addition, the setup script tag, which takes the API to another level again.

Meet the setup script 🔗

In Vue 3 Vue introduced a so-called setup script, which provided a still more concise way to declare components. It can be thought of as a ninja version of the Composition API.

Here's what it looks like:

<template> <p>{{msg}}</p> <MyButton /> </template> <script setup> import MyButton from './MyButton.vue' const msg = 'Hello!' </script>

This is a simple example, but already shows one of the key benefits of the setup script…

Bindings auto-exposed to template 🔗

In part one we saw how it was necessary to return from the setup() function an object of bindings to make available to our template. But notice from the example above that, with the setup script, this isn't necessary (indeed, there's no function to return from.)

This is because all top-level bindings - that is to say, any variables and functions declared in the top level of the script, along with any imports - are automatically exposed to the template.

In our example, this means the variable msg is automatically translated to a piece of component data of the same name.

Recall from part one that when using the setup() hook instead, we must return all template bindings via the method's return object, so the above example would instead be:

import MyButton from './MyButton.vue' export default { setup() { const msg = 'Hello!'; return { msg, MyButton } //<-- expose to template } }

Working without the Vue context 🔗

Normally in Vue, we have access to the Vue context (in other words, the object holding most of the Vue API's methods). In the Options API, we accessed this via this; for the Composition API, we saw in part one how the context was passed in automatically as the second argument to setup(), i.e.:

export default { setup(props, ctx) { //now we can access Vue methods via `ctx` } }

With the setup script, however, we don't have access to the Vue context. So how do we do things like emit events, or access template refs? It all comes back to that central concept of the Composition API: Imported functions.

Imagine the below is a child component hosted by a parent component.

<template> <button @click='click()'>Click me!</button> </template> <script setup> import { defineEmits } from 'vue' const emit = defineEmits(['foo']); const click = () => emit('foo', 'bar'); </script>

As you can see, with the setup script-flavour of the Composition API we define emits via an imported method, defineEmits(). This allows us to specify, as an array, the custom events we want to define, and returns a handler we can later to use to actually fire those events, as we do in the click() method.

Actually, there's no need to manually import defineEmits(); Vue automatically makes this method available when using the setup script, along with its close relative defineProps(), used to define and access incoming props.

No need to declare child components 🔗

Normally in Vue, unless you have something in your build config that auto-imports components, components must declare which child components they use.

Using the setup() method approach to the Composition API, this looks like:

<template> <MyComponent /> </template> <script> import MyComponent from './MyComponent.vue' export default { components: {MyComponent} } </script>

With the setup script, however, this isn't necessary. Remember, the setup script automatically exposes to the template all top-level bindings from the setup script. MyComponent is one such binding, and so it's automatically available to use as a child component in our template.

Async setup 🔗

The keen-eyed reader may have spotted an apparent problem with the setup script. If there's no actual function, as there is with the setup() function approach we met in part one, how do we handle async setup?

Async setup allows us to load a component asynchronously, provided it's wrapped in a suspense component. This is still considered an experimental feature at the time of writing.

The answer is Vue supports a top-level await within the setup script, like so:

<script setup> const data = await fetch('/api'); //woop - I'm now an async component </script>

This looks like invalid JavaScript; normally await is valid only inside a function, and a function prefixed with async at that. However Vue handles that part for us, when it compiles our JavaScript.

So that's it for part two! In the third and final part (soon) we'll look Composable Functions - the feature from which the Composition API takes its name. See you there!

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!