The composition API introduced in VueJS 3.0 has been controversial, to say the least.
In the newest version of Vue (as of the time of writing), the simple object based syntax has been replaced with a new, programmatic approach that, while arguably more powerful and extensible, comes at the cost of complexity.
Here are some tips that you can use to hopefully make the migration for your team as smooth as possible
Though the syntax may be significanly different, at the end of the day Vue is still largely the same reactive component based framework you already know and love.
Components still accept outside data using props. Data still flows downwards.
Components still emit messages to parent components. You must now explicitly define all emitted messages, though.
Computed functions still run only when data has changed allowing for dynamic template data
Data is still reactive and triggers templates to re render upon value change
To use the composition API you simply add a new key called "setup" to the object exported in your components.
<script>
export default {
name: "MyComponent",
props: {
name: {
type: String,
default: "Andrew"
}
},
setup(props) {
// Instead of a data function we now wrap individual variables with refs
const buttonPressed = ref(false);
// Computed functions are now created by passing a callback to the computed function
const helloString = computed(() => `Hello ${props.name}`);
// functions are now declared as const variables instead of within the methods attribute of the returned object
const buttonClicked = function() {
// Values inside reactive refs must be accessed using .value attribute of the ref object
buttonPressed.value = true;
}
// Return all data you need to build your component in an object
return {
buttonPressed,
helloString,
buttonClicked
}
}
}
</script>
<template>
<div>
<input type="button" @click.prevent="buttonClicked" value="Click Here" />
<span v-if="buttonPressed">
{{ helloString }}
</span>
</div>
</template>
At first glance, the composition API setup method might seem like a baffling change. Yeah, it provides a bit more freedom than what could be achieved with the attribute based syntax of Vue and Vue 2, but it comes at the expense of great complexity.
However, once you see the "script setup" syntax, this change starts making a bit more sense.
## The new script setup syntax
The "script setup" syntax is a compiler level directive that you can use within your single file vue components.
Instead of adding a setup function to the object returned, you can now just write your setup function directly and the compiler will convert this into the correct syntax at compile time.
<script setup>
// defineProps is a compile time directive that automatically converts this function into the object syntax used in previous
// versions of vue
const props = defineProps({
name: {
type: String,
default: "Andrew"
}
});
// Instead of a data function we now wrap individual variables with refs
const buttonPressed = ref(false);
// Computed functions are now created by passing a callback to the computed function
const helloString = computed(() => `Hello ${props.name}`);
// functions are now declaed as const variables instead of within the methods attribute of the returned object
const buttonClicked = function() {
// Values inside reactive refs must be accessed using .value attribute of the ref object
buttonPressed.value = true;
}
// No need to return anything like the setup() function, all refs in scope will
// be exported automatically. Neat!
</script>
<template>
<div>
<input type="button" @click.prevent="buttonClicked" value="Click Here" />
<span v-if="buttonPressed">
{{ helloString }}
</span>
</div>
</template>
This turns our single file components into a nice hybrid of OO-like syntax while taking advantage of Javascripts powerful "functional first" approach to development. There are downsides, though. This new syntax no longer "holds your hand" like the object syntax did. It's now up to you to decide how you want to structure your codebase, for better or worse. Which leads us to my next tip
The "script setup" syntax leaves it up to you to decide how you want your components to be structured. That doesn't mean your work has to be a mess. All of your components can be organized in exactly the same way so that when you have to go back to your previous work to fix something or add additional functionality, you already know exactly how to read this piece of code before you even open it.
Heres the structure that I use for every component that I write.
<script setup>
// Props
// Emits
// Modals
// Setup
// Data
// Watchers
// Computed
// Hooks
// Methods
</script>
<template>
<span>Templates go at the bottom</span>
</template>
Ideally this would be enforced by an eslint rule but I haven't researched whether this exists yet. If not I plan to make my own in the future.
I loved renderless functions in vue2. Any limitation of the options API could easily be fixed by making a renderless component that you added to the template of your component. It made development simple and fun. Components felt more like building something with Lego blocks than doing actual work.
You can still use renderless components in vue 3 but there is another alternative that should be considered first, composables. Composables are a new way to reuse functionality in vue 3 by passing reactive variables back from a function that you can call from any component. Composables avoid added the performance hit your application receives when using renderless components. Theres also no reason you can't use composables inside of your renderless components if you really wanted to.
Consider the code snippet below. Its a simple component that accepts a first and last name via component props and displays a name and a greeting. The greeting changes if you click the button.
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
first:{
type: String,
default: 'John'
},
last:{
type: String,
default: 'Smith'
},
});
const greeting = ref('hello');
const greetUser = computed(() => `${ greeting } ${props.first} ${props.last}`);
const changeGreeting = function () {
const theGreeting = greeting.value;
if(theGreeting === 'hello') {
greeting.value = 'goodbye';
}
else {
greeting.value = 'hello';
}
};
</script>
<template>
<div>
<input type="button" @click.prevent="changeGreeting" value="Change Greeting" />
<span>{{ greetUser }}</span>
</div>
</template>
Output:
hello John Smith
Output after clicking button:
goodbye John Smith
If we needed this functionality to be reusable, we could do so very easily using a composable.
import { ref, computed } from 'vue'
// The functionality of the original component has been moved over to a reusable function.
// useXYZ is the recommended syntax for your composables
export default function useGreeting(props) {
const greeting = ref('hello');
const greetUser = computed(() => `${ greeting } ${props.first} ${props.last}`);
const changeGreeting = function () {
const theGreeting = greeting.value;
if(theGreeting === 'hello') {
greeting.value = 'goodbye';
}
else {
greeting.value = 'hello';
}
};
// Return any variables and functions necessary to use this functionality at the end of the function
return {
greetUser,
changeGreeting
}
}
Then in our original component, we can now import and run the useGreeting function to extract its contents
<script setup>
import { useGreeting } from './greeting.js'
const props = defineProps({
first:{
type: String,
default: 'John'
},
last:{
type: String,
default: 'Smith'
},
});
// Javascript destructuring is used to pull the data from the composable into the local scope of the component.
// These variables will automatically be available to use in our template. Note that we are passing the props
// from our component to the composable as a function argument. Any data required in the composable can be
// passed in this way.
const { greetUser, changeGreeting } = useGreeting(props);
</script>
<template>
<div>
<input type="button" @click.prevent="changeGreeting" value="Change Greeting" />
<span>{{ greetUser }}</span>
</div>
</template>
The new composition API introduced in Vue 3 is a big change, but the underlying functionality is largely the same. In addition to these tips, I would recommend reading the documentation for Vue 3 available on the Vue.js website. Best of luck migrating your codebase!
I show off a common Trait issue and what I do to work around it
The strategy I used to rewrite an entire legacy PHP application in Laravel by myself
A story of an internship I did with a governmenmental IT Dept