Vue3 Composition API
1. Why the Composition API
1.1. Why the Composition API
If you have some confusion about the new Vue3 Composition API, by the end of this course it should be clear why the limitations of Vue 2 led to it and how it solved some of the problems for us. Currently, you may encounter three limitations when using Vue 2:
- As your components get larger, readability becomes more and more difficult.
- Current patterns of code reuse all have drawbacks.
- Vue 2 provides limited TypeScript support.
I’ll cover the first two in detail, so it’s obvious what the new API solves.
1.2. Large components can be hard to read & maintain.
To solve this problem, let’s consider the components of the product that can be searched on our website.The component code using standard Vue component syntax would look like this:What happens when we also want to add the ability to sort search results. Our code looks like this:Fine, until we add search filters and paging to the same component. Our new functionality will contain code snippets that we’ll split between all the component options (Components, props, data, computed, Methods, and lefecycle Methods). If we visualize this using colors (below), you’ll see how our feature code will be split, making it harder for our components to read and parse which feature code to use with which feature.As you can imagine (as shown on the right), if we could keep the functional code together, our code would be more readable and thus easier to maintain. If we went back to the original example and used the Composition API to group things together, the result would look like this:To do this using the setup () method, as shown above, we need to use the new Vue 3 Composition API. The code in setup () is a new syntax that I’ll cover in a later lesson. It’s worth noting that this new syntax is completely optional, and the standard way of writing Vue components is still perfectly valid. I know when I first saw it, I wondered, “Wait, does that mean I created a giant setup method and put all the code in it? How did that happen?” No, don’t worry. It won’t happen. When you use the Composition API to organize components by function, you group functions into composite functions that can be called by setting methods, as follows:Now we can organize our components using logical concerns (also known as “functions”). However, this does not mean that our user interface will be made up of fewer components. You will still use good component design patterns to organize your application:Now that you know how the Component API makes large components more readable and maintainable, we can move on to the second limitation of Vue 2.
1.3. Thereโs no perfect way to reuse logic between components.
In terms of reusing code across components, there are three good solutions for doing this in Vue 2, but each solution has its limitations. Let’s walk through our example. First, there are Mixins. advantages
- Mixins can be organized by function.
disadvantages
- They are prone to conflicts, and you can end up with property name conflicts.
- It is not clear how the mixins interact (if they exist).
- Mixins are not easy to reuse if you configure them for use among other components.
This last term gives us a lookMixins factory, which is a function that returns a custom version of the Mixin.As you can see above, the Mixin factory allows us to customize Mixins by sending configurations. We can now configure this code to be used in multiple components.advantages
- Now we can configure the code so it can be easily reused.
- We have a much clearer relationship with how we interact with Mixins.
disadvantages
- Naming intervals requires strict convention and discipline.
- We still have implicit attribute additions, which means we have to look inside a Mixin to find out what attributes it exposes.
- There is no instance access at run time, so Mixin factories cannot be generated on the fly.
Fortunately, there is another solution that is often the most usefulScope slot: advantages
- Addresses almost all of the shortcomings of Mixins.
disadvantages
- Your configuration ends up in the template, which ideally should contain only what we want to render.
- They increase the indentation of the template, which reduces readability.
- Exposed properties are only available in the template.
- Because we used three components instead of one, performance was reduced.
As you can see, each solution has its limitations. Vue 3’s Composition API gives us a fourth way to extract reusable code, which might look something like:Now we will create the component using functions inside the Composition API that will be imported and used in any setup methods we need to configure.advantages
- We’re writing less code, so it’s easier to pull functionality from components into functionality.
- Because you are already familiar with the functionality, it builds on your existing skills.
- It is more flexible than Mixins and Scoped slots because they are just features.
- Intellisense, auto-complete and typing already work in your code editor.
disadvantages
- You need to learn new low-level apis to define compositing functionality.
- There are now two ways to write components, not just the standard syntax.
Hopefully you now understand the “why” behind the ingredient API, which I know I didn’t at first. In the next lesson, I’ll delve into the new syntax for components.
2. Setup & Reactive References
At this point, you might be wondering what the new Composition API syntax looks like, and we’ll dive into it. First, we want to figure out when to use it, and then we’ll learn about setting up functional and reactive references or references. Also, if you don’t already have one, you may want to get Vue Mastery’s Vue 3 Cheat sheet. ** Disclaimer: ** If you haven’t caught up yet, the Composition API is purely additive and timeless. You can code Vue 3 just as you code Vue 2.
2.1 When to use the Composition API?
If any of the following conditions are met:
- You need the best TypeScript support.
- Components are too large and need to be organized by function.
- You need to reuse code between other components.
- You and your team prefer alternative grammars.
** Disclaimer 2: ** The following example is very simple. Using the Composition API to construct components so simply is not necessary, but makes it easier to learn. Let’s start with a very simple component written using the normal Vue 2 API that works in Vue 3.
<template>
<div>Capacity: {{ capacity }}</div>
</template>
<script>
export default {
data() {
return {
capacity: 3}; }};</script>
Copy the code
Notice, I have a simple onecapacity
Property, it’s reactive. Vue knows every property in the object returned with the Data property and makes them reactive. This way, when these reactive properties change, the component that uses this property is rerendered. H, right? In the browser, we see:
2.2 Using the Setup Function
In Vue 3 using the Composition API, I’ll start by writing setup in a way I’ve seen before:
<template>
<div>Capacity: {{ capacity }}</div>
</template>
<script>
export default {
setup() {
// more code to write}};</script>
Copy the code
Setup evaluates execution prior to any of the following options:
- components
- props
- data
It is also worth mentioning setup, which, unlike the other Component options, does not have access to “this”. To gain access to the property, we typically use this property setup with two optional parameters. The first is Reactive and can watch props, such as:
import { watch } from "vue";
export default {
props: {
name: String
},
setup(props) {
watch(() = > {
console.log(props.name); }); }};Copy the code
The second argument is context, which can access a bunch of useful data:
setup(props, context) {
context.attrs;
context.slots;
context.parent;
context.root;
context.emit;
}
Copy the code
But let’s go back to our example. Our code needs a Reactive reference:
2.3 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>
Copy the code
Const Capacity = ref(3) is creating a “Reactive Reference”, which basically wraps the raw integer (3) in an object that will allow us to trace and make changes. Remember that previously our data() option had wrapped our raw (capacity) in an object. Also: The Composition API allows us to declare component-independent reactive primitives, which is our approach. As a final step, we need to explicitly return an object whose properties need to be rendered correctly by the template.
<template>
<div>Capacity: {{ capacity }}</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const capacity = ref(3);
return{ capacity }; }};</script>
Copy the code
This returned object is a method of how do we expose what data we need to access inrenderContext
. It’s a little verbose, but it’s intentional. It helps in long-term maintenance because we can control what is exposed to the template and keep track of where template attributes are defined. Now, here we go:
Using Vue 3 with Vue 2
It’s worth noting that you can use this plug-in today to combine the Vue 3 Composition API with Vue 2. After installing and configuring it on the Vue 2 application, you will use the same syntax taught above, but with a few modifications. Instead of
import { ref } from "vue";
Copy the code
Can you write
import { ref } from "@vue/composition-api";
Copy the code
In case you’re wondering, this is how I tested all of the above code. Next, we’ll learn how to write component methods using this new syntax.
3. Methods
Now that we’ve learned how to create reactive references, the next building block for our components is to create methods. If you haven’t already downloaded the Vue 3 Composition API quick-check, now is a good time. Here is our current code as follows:
<template>
<div>Capacity: {{ capacity }}</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const capacity = ref(3);
return{ capacity }; }};</script>
Copy the code
If we wanted to add a method that would allow us to increase capacity with buttons, we could write the following in regular component syntax:
methods: {
increase_capacity() {
this.capacity++; }}Copy the code
But how do we use the new Vue 3 Composition API? Well, we first define a function in the setup method, return the method so that our component can access it, and then use it inside the button:
<template>
<div>
<p>Capacity: {{ capacity }}</p>
<button @click="increaseCapacity()">Increase Capacity</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const capacity = ref(3);
function increaseCapacity() { // <--- Our new function
// TBD
}
return{ capacity, increaseCapacity }; }};</script>
Copy the code
Yes, when we need methods, we simply use the Composition API to create them as functions. But how do you think we can increase capacity from within the setup method? You might guess:
function increaseCapacity() {
capacity++;
}
Copy the code
It won’t work and it will go wrong. Remember, capacity is a reaction reference, an object that encapsulates our integers. Adding an object will not work. In this case, we need to increment the inner integer encapsulated by the value reactive reference. We can do this by accessing capacity. Value.
function increaseCapacity() {
capacity.value++;
}
Copy the code
If we check in in the browser, everything is fine now:Everything here belongs to it. However, if you look at the template, you’ll notice that when printing the capacity:
<p>Capacity: {{ capacity }}</p>
Copy the code
We don’t have to write capacity-value, and you might wonder why. It turns out that Vue ref automatically exposes the internal value when it is found in the template, so you never need.value to be called inside the template.
4. Computed Properties
Let’s learn how to create computed properties using the new Composition API syntax. First, though, we need to add it to the sample application, so now we have a list of people participating in the activity.
<template>
<div>
<p>Capacity: {{ capacity }}</p>
<button @click="increaseCapacity()">Increase Capacity</button>
<h2>Attending</h2>
<ul>
<li v-for="(name, index) in attending" :key="index">
{{ name }}
</li>
</ul>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const capacity = ref(4);
const attending = ref(["Tim"."Bob"."Joe"]); // <--- New Array
function increaseCapacity() {
capacity.value++;
}
return{ capacity, attending, increaseCapacity }; }};</script>
Copy the code
Notice that we now have a new actor array, and we print out each actor. Our website looks like:To create a need for calculated properties, let’s change the way we print capacity in the template:
<template>
<div>
<p>Spaces Left: {{ spacesLeft }} out of {{ capacity }}</p>.Copy the code
Note that spacesLeft will display the amount of space left in the event in terms of capacity minus the number of participants. If you were to create a calculated property using normal component syntax, it might look something like this:
computed: {
spacesLeft() {
return this.capacity - this.attending.length; }}Copy the code
But how do we create it using the new Composition API? It looks something like this:
<template>
<div>
<p>Spaces Left: {{ spacesLeft }} out of {{ capacity }}</p>
<h2>Attending</h2>
<ul>
<li v-for="(name, index) in attending" :key="index">
{{ name }}
</li>
</ul>
<button @click="increaseCapacity()">Increase Capacity</button>
</div>
</template>
<script>
import { ref, computed } from "vue";
export default {
setup() {
const capacity = ref(4);
const attending = ref(["Tim"."Bob"."Joe"]);
const spacesLeft = computed(() = > { // <-------
return capacity.value - attending.value.length;
});
function increaseCapacity() {
capacity.value++;
}
return{ capacity, attending, spacesLeft, increaseCapacity }; }};</script>
Copy the code
As you can see in the code above, we arecomputed
Imported from the Vue API, and then using it, passing in an anonymous function and setting it to a constant equal tospacesLeft
. We then return it to the object from the setup function so that our template can access it. Now in the browser, we see:
5. The Reactive Syntax
So far, we’ve used reactive references to wrap JavaScript primitives in objects to make them reactive. However, there is another way to wrap these primitives in objects. The specific usereactive
Syntax. Below, you can see our example using Reactive References on the left and others on the rightreactive
Syntax.
As you can see on the right, we create a new Event constant that takes a normal JavaScript object and returns a reaction object. Data using this option might look familiar in regular component syntax, where we also send an object. However, as you saw above, we can also send calculated properties to this object. You should also note that with the syntax.value, you no longer need to write when accessing properties. This is because we are only accessing the Event object property on the object. You should also notice that we return the entire object Setup at the end of the function event. Note that both grammars are fully valid and neither is considered a “best practice.” In order for our code to work, we need to update the template code as follows:
<p>Spaces Left: {{ event.spacesLeft }} out of {{ event.capacity }}</p>
<h2>Attending</h2>
<ul>
<li v-for="(name, index) in event.attending" :key="index">
{{ name }}
</li>
</ul>
<button @click="increaseCapacity()">Increase Capacity</button>
Copy the code
Notice how we now call event. To access the property.
5.1 Destructuring?
When I first saw the following code:
return { event, increaseCapacity }
Copy the code
I wonder if there is any way to decompose event objects so that you don’t always have to write events in templates.? I’d rather write my template like this:
<p>Spaces Left: {{ spacesLeft }} out of {{ capacity }}</p>
<h2>Attending</h2>
<ul>
<li v-for="(name, index) in attending" :key="index">
{{ name }}
</li>
</ul>
<button @click="increaseCapacity()">Increase Capacity</button>
Copy the code
But how do I break the event? I tried the following two methods and both failed:
return { ...event, increaseCapacity };
return { event.capacity, event.attending, event.spacesLeft, increaseCapacity };
Copy the code
None of this works because splitting the object removes its reactivity. To make this work, we need to be able to split this object into Reactive References to keep it Reactive.
5.2 Introducing toRefs
Fortunately, there is a way to do this using the toRefs method. This method transforms the Reactive object into a normal object, where each property is a Reactive Reference to a property on the original object. Here is the code we completed using this method:
import { reactive, computed, toRefs } from "vue";
export default {
setup() {
const event = reactive({
capacity: 4.attending: ["Tim"."Bob"."Joe"].spacesLeft: computed(() = > {
returnevent.capacity - event.attending.length; })});function increaseCapacity() {
event.capacity++;
}
return{... toRefs(event), increaseCapacity }; }};Copy the code
Notice that I’m importing toRefs, then using it in the return statement, and then destroying the object. This is great!
5.3 Aside
Before continuing, I would like to mention that if our code does not also need to increaseCapacity to return the function with its return value, I can simply write:
return toRefs(event);
Copy the code
This is because our setup method expects us to return an object, which is the object returned, toRefs.
6. Modularizing
The two reasons we might use component apis are to organize components by function and reuse our code among other components. So far, we’ve completed the code examples, so let’s get started. This is our current code, please note that I have changed back to using ** reactive references, ** this syntax seems cleaner to me.
<template>
...
</template>
<script>
import { ref, computed } from "vue";
export default {
setup() {
const capacity = ref(4);
const attending = ref(["Tim"."Bob"."Joe"]);
const spacesLeft = computed(() = > {
return capacity.value - attending.value.length;
});
function increaseCapacity() {
capacity.value++;
}
return{ capacity, attending, spacesLeft, increaseCapacity }; }};</script>
Copy the code
6.1 Extracting oneself into a Composition Function
Sounds simple enough:
<template>
...
</template>
<script>
import { ref, computed } from "vue";
export default {
setup() {
return useEventSpace(); // <--- Notice I've just extracted a function}};function useEventSpace() {
const capacity = ref(4);
const attending = ref(["Tim"."Bob"."Joe"]);
const spacesLeft = computed(() = > {
return capacity.value - attending.value.length;
});
function increaseCapacity() {
capacity.value++;
}
return { capacity, attending, spacesLeft, increaseCapacity };
}
</script>
Copy the code
All I have to do is move all the code to export default {that’s not in my function right now. Setup () This method is now the place to bind the composition functionality together.
6.2 Extracting into a file to reuse the code
If useEventSpace() is a piece of code THAT I might want to use in multiple components, all I have to do is extract this function into my own file and use the export default: ๐use/event-space.vue
import { ref, computed } from "vue";
export default function useEventSpace() {
const capacity = ref(4);
const attending = ref(["Tim"."Bob"."Joe"]);
const spacesLeft = computed(() = > {
return capacity.value - attending.value.length;
});
function increaseCapacity() {
capacity.value++;
}
return { capacity, attending, spacesLeft, increaseCapacity };
}
Copy the code
I use called the folder for the composition function, but you can call it at will. Composables or hooks Other good names. Now, all my component code needs to do is import this composition function and use it.
<template>
...
</template>
<script>
import useEventSpace from "@/use/event-space";
export default {
setup() {
returnuseEventSpace(); }};</script>
Copy the code
6.3 Adding another Composition Function
If we had another combined function (perhaps in use/event-mapping.js) to map our events, and we wanted to use it here, we could write:
<template>
...
</template>
<script>
import useEventSpace from "@/use/event-space";
import useMapping from "@/use/mapping";
export default {
setup() {
return{... useEventSpace(), ... useMapping() } } };</script>
Copy the code
As you can see, sharing composition capabilities between components is simple. In fact, I might send shared data to these functions, such as event data extracted from the API using Vuex.
6.4 Vue 3 Best Practice
In the code above, although it is very efficient, it introduces a problem. It is not clear which objects come from which synthesizers. This is part of the reason we left Mixins behind, which can hide which objects come from which code snippets. For this reason, we might want to write differently using local objects:
<template>
...
</template>
<script>
import useEventSpace from "@/use/event-space";
import useMapping from "@/use/mapping";
export default {
setup() {
const { capacity, attending, spacesLeft, increaseCapacity } = useEventSpace();
const { map, embedId } = useMapping();
return{ capacity, attending, spacesLeft, increaseCapacity, map, embedId }; }};</script>
Copy the code
It is now clear where objects are coming from.
7. Lifecycle Hooks
You may be familiar with the Vue lifecycle hook, which enables us to run code when a component reaches a specific state in execution. Let’s review a typical LifeCycle hook:
- **beforeCreate -** called immediately after instance initialization and before processing options.
- Created – Called after an instance is created.
- **beforeMount-** Before starting DOM installation
- Mounted – called when the instance is mounted (the browser has been updated).
- **beforeUpdate-** Called when reactive data has changed before DOM rerendering.
- **updated-** Called when reactive data has changed and the DOM has been rerendered.
- **beforeDestroy-** Called before destroying the Vue instance.
- Destroyed -** called after the Vue instance is destroyed.
You may not be familiar with two new Vue 2 LifeCycle methods:
- **activated-** Used when the component is enabled internally.
- **deactivated-** Used when the component is closed internally.
- ErrorCaptured -** Called when you catch any errors in a descendant component.
For more details, see the API documentation on the LifeCycle hook.
7.1 Unmounting in Vue 3
In Vue 3 beforeDestroy(), you can also write beforeUnmount(), and discover () as unmounted(). When I asked Evan You about these changes, he mentioned that this is just a better naming convention because Vue will mount _ and _ unload _ components.
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured
} from "vue";
export default {
setup() {
onBeforeMount(() = > {
console.log("Before Mount!");
});
onMounted(() = > {
console.log("Mounted!");
});
onBeforeUpdate(() = > {
console.log("Before Update!");
});
onUpdated(() = > {
console.log("Updated!");
});
onBeforeUnmount(() = > {
console.log("Before Unmount!");
});
onUnmounted(() = > {
console.log("Unmounted!");
});
onActivated(() = > {
console.log("Activated!");
});
onDeactivated(() = > {
console.log("Deactivated!");
});
onErrorCaptured(() = > {
console.log("Error Captured!"); }); }};Copy the code
You might notice two missing hooks. BeforeCreate and Created are not required when using the Composition API. This is because beforeCreate() is created before and after setup() and is called setup(). Therefore, we just need setup() to put code that would normally be placed in these hooks, such as API calls.
7.2 Two New Vue 3 LifeCycle Methods
There are two other observers in Vue 3. The Vue2 Composition API plug-in does not yet implement these observers (as OF this writing), so they cannot be used without using Vue 3 source code.
- **onRenderTracked-** called during rendering when a reaction dependency is first accessed in a render function. This dependency will now be traced. This helps you see which dependencies to track for debugging purposes.
- **onRenderTriggered-** Called when a new rendering is triggered, allowing you to check what dependencies triggered the component to rerender.
I’m excited to see what optimization tools can be created using these two hooks.
8. Watch
Let’s look at another simple example using our component API. Some of the code here has a simple search input box that calls the API with the search text and returns the number of events that match the input results.
<template>
<div>
Search for <input v-model="searchInput" />
<div>
<p>Number of events: {{ results }}</p>
</div>
</div>
</template>
<script>
import { ref } from "@vue/composition-api";
import eventApi from "@/api/event.js";
export default {
setup() {
const searchInput = ref("");
const results = ref(0);
results.value = eventApi.getEventCount(searchInput.value);
return{ searchInput, results }; }};</script>
Copy the code
Using this code, the following happens when we use forms:As you can see, it doesn’t seem to work. This is because of our API calling coderesults.value = eventApi.getEventCount(searchInput.value);
In the first timesetup()
The runtime is called only once. When wesearchInput
When it updates, it doesn’t know it will fire again.
Solution: 8.1 watchEffect
To solve this problem, we need to use watchEffect. This will run our function on the next scale, tracing its dependencies in a passive manner and re-running it when the dependencies change. Like this:
setup() {
const searchInput = ref("");
const results = ref(0);
watchEffect(() = > {
results.value = eventApi.getEventCount(searchInput.value);
});
return { searchInput, results };
}
Copy the code
Therefore, the first time you run it, it will use reactivity to start tracingsearchInput
And it will rerun our API call when the update is maderesults
. Due to theresults
Has been used in our template, our template will be re-rendered.If I want to be more specific about the source I want to monitor, I can usewatch
Instead ofwatchEffect
, such as:
watch(searchInput, () = >{... });Copy the code
Alternatively, if I need to access the new and old values of the monitored item, I can write:
watch(searchInput, (newVal, oldVal) = >{... });Copy the code
8.2 Watching Multiple Sources
If you want to view two reaction references, you can send them to an array:
watch([firstName, lastName], () = >{... });Copy the code
Now, if any of them are changed, the code will run again. I can also access their old and new values by:
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) = >{... });Copy the code
9. Sharing State
Now that we know the basic syntax of the Composition API, we can use it to extract some reusable code from components. When using API calls, many times we might want to build a lot of code and functionality around the call. Specifically, load states, error states, and try/catch blocks. Let’s take a look at this code and extract it correctly using the Composition API. I’ve created a code sample from the previous lesson: ๐/ SRC/app.js
<template>
<div>
Search for <input v-model="searchInput" />
<div>
<p>Loading: {{ loading }}</p>
<p>Error: {{ error }}</p>
<p>Number of events: {{ results }}</p>
</div>
</div>
</template>
<script>
import { ref, watch } from "@vue/composition-api";
import eventApi from "@/api/event.js";
export default {
setup() {
const searchInput = ref("");
const results = ref(null);
const loading = ref(false);
const error = ref(null);
async function loadData(search) {
loading.value = true;
error.value = null;
results.value = null;
try {
results.value = await eventApi.getEventCount(search.value);
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
watch(searchInput, () = > {
if(searchInput.value ! = ="") {
loadData(searchInput);
} else {
results.value = null; }});return{ searchInput, results, loading, error }; }};</script>
Copy the code
It looks like this in the browser:
9.1 Now with Shared State
This is a very common pattern in a Vue application where I have an API call and I need to consider the result, load, and error status. How do I extract it to use the composite API? First, I can create a new file and extract common functions. ๐ / composables/use – promise. Js
import { ref } from "@vue/composition-api";
export default function usePromise(fn) { // fn is the actual API call
const results = ref(null);
const loading = ref(false);
const error = ref(null);
const createPromise = async(... args) => {// Args is where we send in searchInput
loading.value = true;
error.value = null;
results.value = null;
try {
results.value = awaitfn(... args);// Passing through the SearchInput
} catch (err) {
error.value = err;
} finally {
loading.value = false; }};return { results, loading, error, createPromise };
}
Copy the code
Notice how this function holds reactive references and wraps the function of the API call, along with all the parameters that need to be passed to the API call. Now use this code: ๐/ SRC/app.js
<template>
<div>
Search for <input v-model="searchInput" />
<div>
<p>Loading: {{ getEvents.loading }}</p>
<p>Error: {{ getEvents.error }}</p>
<p>Number of events: {{ getEvents.results }}</p>
</div>
</div>
</template>
<script>
import { ref, watch } from "@vue/composition-api";
import eventApi from "@/api/event.js";
import usePromise from "@/composables/use-promise";
export default {
setup() {
const searchInput = ref("");
const getEvents = usePromise(search= >
eventApi.getEventCount(search.value)
);
watch(searchInput, () = > {
if(searchInput.value ! = ="") {
getEvents.createPromise(searchInput);
} else {
getEvents.results.value = null; }});return{ searchInput, getEvents }; }};</script>
Copy the code
That’s all, we get the same functionality as shown above. In particular, notice how easy it is to use the reaction state (load, error, and result) in my use-promise.js file that is used in my component. Now, when I have another API call, I can use promises.
9.2 Caveat
When I did this by members of the Vue core team, they called attention to… GetEvents. Specifically, I’m not supposed to destroy objects. GetEvents namespaces the data without breaking it, making it easier to encapsulate and making it clear where the data comes from in the components that use it. It might look something like:
<template>
<div>
Search for <input v-model="searchInput" />
<div>
<p>Loading: {{ getEvents.loading }}</p>
<p>Error: {{ getEvents.error }}</p>
<p>Number of events: {{ getEvents.results }}</p>
</div>
</div>
</template>
<script>.export default {
setup(){...return{ searchInput, getEvents }; }};</script>
Copy the code
However, when I run it in a browser, I get the following result:It seems that Vue 2 with the composition API cannot correctly recognize my reaction reference union.value
Call as it should be called. I can be.value
This problem is resolved by manually adding orRs using Vue 3. I tested the code with Vue 3, and sure enough, it saw Reactive References and displayed them correctly.value
.
10. Suspense
When we code a Vue application, we make heavy use of API calls to load back-end data. It is good user interface practice to let the user know that the data is loading while we are waiting for the API data to load. This is especially necessary if the user has a slow Internet connection. Typically in Vue, we use a lot of V-ifand V-else statements to display a portion of THE HTML while waiting for the data to load, and then switch it out when the data is loaded. Things get more complicated when we have multiple components making API calls, and we want to wait until all the data is loaded before displaying the page. However, Vue 3 comes with a React 16.6-inspired alternative called Suspense. This allows you to wait for any asynchronous work (such as making data API calls) to complete before displaying the component. Suspense is a built-in component that we can use to wrap two different templates, as follows:
<template>
<Suspense>
<template #default>
<! -- Put component/components here, one or more of which makes an asychronous call -->
</template>
<template #fallback>
<! -- What to display when loading -->
</template>
</Suspense>
</template>
Copy the code
Suspense load will first try to render what it finds
. If at any time a component with a setup return promise function is found, or an asynchronous component is found (a new feature in Vue 3), it will render instead,
until all promises are resolved. Let’s look at a very basic example:
<template>
<Suspense>
<template #default>
<Event />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
<script>
import Event from "@/components/Event.vue";
export default {
components: { Event },
};
</script>
Copy the code
Here you can see that I am loading the event component. It looks similar to previous classes:
<template>
...
</template>
<script>
import useEventSpace from "@/composables/use-event-space";
export default {
async setup() {
const { capacity, attending, spacesLeft, increaseCapacity } = await useEventSpace();
return{ capacity, attending, spacesLeft, increaseCapacity }; }};</script>
Copy the code
Pay special attention to minesetup()
Method marked asasync
And myawait useEventSpace()
The call. Obviously,useEventSpace()
There’s an API call inside the function, and I’m going to wait for it to return. Now, when I load the page, I see that it’s loading… Message until the API call promise is resolved and the generated template is displayed.
10.1 Multiple Async Calls
Suspense has the advantage that I can make multiple asynchronous calls, and Suspense will wait for all those calls to be resolved to display anything. So, if I put:
<template>
<Suspense>
<template #default>
<Event />
<Event />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
Copy the code
Notice these two events? Suspense will now wait for them all to resolve before appearing.
Deeply struck by Nested Async Calls
Even more powerful, I might have a deeply nested component with asynchronous calls. The suspension waits for all asynchronous calls to complete before loading the template. Therefore, you can have a load screen on your application that waits for multiple parts of the application to load.
10.3 What about errors?
If the API call doesn’t work properly, it usually needs to fall back, so we need some kind of error screen as well as a load screen. Fortunately, the Suspense syntax allows you to use V-if with older versions, and we have a new onErrorCaptured lifecycle hook to listen for errors:
<template>
<div v-if="error">Uh oh .. {{ error }}</div>
<Suspense v-else>
<template #default>
<Event />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
<script>
import Event from "@/components/Event.vue";
import { ref, onErrorCaptured } from "vue";
export default {
components: { Event },
setup() {
const error = ref(null);
onErrorCaptured((e) = > {
error.value = e;
return true;
});
return{ error }; }};</script>
Copy the code
Notice the top DIV, and v-else tags in suspense. Also note the callback in the onErrorCapturedsetup method. In case you’re wondering, true returns onErrorCaptured to prevent errors from spreading further. This way, our users will not see errors in their browser console.
10.Creating Skeleton Loading Screens
useSuspenseTags make it very easy to create things like skeleton loading screens. You know, like this: Your skeleton will go into your frame,<template #fallback>
And the rendered HTML will go into your frame<template #default>
. Very simple!
11. Teleport
Vue’s component architecture enables us to build user interfaces into components that nicely organize the business logic and presentation layers. However, in some cases, some HTML in a component needs to be rendered elsewhere. Such as:
- Need fixed or absolute positioning and z-index style. For example, a common pattern is to combine UI components such as patterns
</body>
Place it before the tag to ensure it is placed correctly before all other parts of the page. - When our Vue application is running on a small part of a Web page (or widget), there are times when we might want to move the component to another location in the DOM outside of the Vue application.
11.1 Solution
The solution Vue 3 provides is the Teleport component. Previously, it was called “portal,” but the name has been changed to Teleport to avoid conflicts with future elements that
.<div id="app"></div>
<div id="end-of-body"></div>
</body>
</html>
Copy the code
Then, let’s try passing some text #end-of-body from inside our Vue application to the outside of that div. /src/App.vue
<template>
<teleport to="#end-of-body">
This should be at the end.
</teleport>
<div>
This should be at the top.
</div>
</template>
Copy the code
Notice that in the transport line, we specify the div to move the template code to, and if this is done correctly, the text at the top should be moved to the bottom. Sure enough, it can: (width = 300)
11.2 Teleport Options for To
Our to property only needs to be a valid DOM query selector. In addition to what I did above with ID, here are three more examples. Class selectors
<teleport to=".someClass">
Copy the code
Data selector
<teleport to="[data-modal]">
Copy the code
With the data attribute, our target div might look like this: Dynamic selector You can even bind dynamic selectors to add colons if desired.
<teleport :to="reactiveProperty">
Copy the code
11.3 Disabled State
Patterns and other pop-ups usually start to hide until they appear on the screen. Therefore, transport is disabled, where the content remains in the original component. It will not move to the target location until transport is enabled. Let’s update the code so that it can switch showText, as follows:
<template>
<teleport to="#end-of-body" :disabled=! "" showText">
This should be at the end.
</teleport>
<div>
This should be at the top.
</div>
<button @click="showText = ! showText">
Toggle showText
</button>
</template>
<script>
export default {
data() {
return {
showText: false}; }};</script>
Copy the code
As you can see, with the switch, the content inside the transport is moved from inside the component to outside the component: <01-disable.gif width = 250> If we examine the source in real time, we can see that the content is actually moving from one place to another in the DOM. <02-devtools.gif width = 367>
11.4 Automatically Saving the State
When transport changes from disabled to enabled, DOM elements are reused, so they remain completely in their current state. This can be illustrated by transmitting the video being played.
<template>
<teleport to="#end-of-body" :disabled=! "" showText">
<video autoplay="true" loop="true" width="250">
<source src="flower.webm" type="video/mp4">
</video>
</teleport>
<div>
This should be at the top.
</div>
<button @click="showText = ! showText">
Toggle showText
</button>
</template>
<script>
export default {
data() {
return {
showText: false}; }};</script>
Copy the code
As you can see in the video below, the state of the video remains the same as it moves between positions. <03-video.gif width = 266>
11.5 Hiding in the Text
If the content we have in teleporting is modal, we may not want to display it before it is active. Now “This should be at the end.” Display inside the component even if showText is false. We can disable the display by adding v-if.
<template>
<teleport to="#end-of-body" :disabled=! "" showText" v-if="showText">
This should be at the end.
</teleport>.Copy the code
Now, our text is displayed only when showText is true and is therefore sent to the bottom of the page. < 04-V – If width = 250>
11.6 Multiple Teleports into the Same Place
Which makes me wonder, what happens when you teleport two things to the same location? I can see (especially with modes) that you might want to transmit more than one thing. Let’s try a simple example by simply creating a showText2.
<template>
<teleport to="#end-of-body" :disabled=! "" showText" v-if="showText">
This should be at the end.
</teleport>
<teleport to="#end-of-body" :disabled=! "" showText2" v-if="showText2">
This should be at the end too.
</teleport>
<div>
This should be at the top.
</div>
<button @click="showText = ! showText">
Toggle showText
</button>
<button @click="showText2 = ! showText2">
Toggle showText2
</button>
</template>
<script>
export default {
data() {
return {
showText: false.showText2: false}; }};</script>
Copy the code
You can see in the video below that it works as expected and is added after the switch. Interestingly, it simply adds elements based on the element clicked first. <width = 300px>
11.7 Conclusion
As you can see, using Teleport gives you a way to keep your code in the same component while moving the code snippet to other parts of the page. In addition to the obvious solution of using it for modality (it needs to be displayed at the top of the rest of the page, above the tag), I’m glad to see that this Vue 3 functionality is available in practice. For more detailed written instructions, see RFC. Thank you for completing our first Vue 3 course. Next, I’ll look at the Vue 3 reactivity course, explaining some of the core concepts of Vue 3’s new reactivity engine.