Vue Slots Explained

Posted onby admin

I recently figured out how to implement nested slots recursively, including how to do this with scoped slots.

It all started when I wanted to see if I could build a component that replicated the v-for directive, but only using the template.

To make this story short: I did.

The v-slot directive was introduced in Vue 2.6.0, offering an improved, alternative API to the still-supported slot and slot-scope attributes. The full rationale for introducing v-slot is described in this RFC. Second, Vue will create an entirely new component when we render the second time. Vue will destroy the first one and create a new one. Vue will destroy the first one and create a new one. This means that our new my-component will go through all of its lifecycles as normal — created, mounted, and so on.

Rendering a list without using the v-for directive or render functions. Supports scoped slots as well... pic.twitter.com/fHlbJdbrEe

— Michael Thiessen (@MichaelThiessen) June 27, 2019

(follow me to get my tweets as I learn new stuff!)

And yes, it supports slots and scoped slots — and could be made to support named slots as well.

We can use it like this:

The first one will print out the list of items normally, while the second one will wrap each item in a <strong> tag.

It's not a very useful component, but I like to have fun experimenting as I find I learn the most that way.

Let's take a look at what's going on here.

Looping without loops

Normally when we'd want to render out a list of elements or components we'd use the v-for directive. But we want to get rid of it completely.

So, how do we render out a list of items without using a loop?

Any guesses?

The answer is: recursion.

We can use recursion to represent a list of items.

It's actually not too complicated. I'll show you how this works.

Representing a list recursively

One of my favourite courses in university was Programming Language Concepts.

The most interesting part of it to me was exploring functional programming and logic programming, and seeing the differences to imperative programming (Javascript and most popular languages are imperative).

This was the course that really opened my eyes on how to use recursion, since with pure functional languages everything is recursion.

Anyways, from that course I learned that you can represent a list recursively.

Instead of using an array, each list is a value (the head), and another list (the tail).

So if you wanted to represent the list [1, 2, 3], it would be represented recursively as:

Vue Slots Explained

We have to end the list somehow, so we use null instead of another array (an empty array would work too).

Maybe you're starting to see where I'm going with this...

We can use this concept and apply it to our components. Instead, we'll be nesting our components recursively in order to represent our list.

We'll end up rendering something like this. Notice the nested structure of our 'list':

Admittedly, this isn't the exact same thing as what v-for would render, but that's not exactly the point of this exercise either.

So now that we've worked out what we're doing, how do we create it?

First, I'd like to challenge you to try building it yourself before moving on. I think you'll be able to figure it out, and you'll learn more that way than by just reading how I did it.

Okay, now it's time for me to show how I solved this problem.

Building the component

First, we'll tackle rendering out the list of items recursively. Once we get that working, we'll come back and figure out how to make the slots work.

Using recursion to render lists

Instead of using the recursive list we showed before, we'll work with a plain array:

We have to cover 2 cases here:

  1. Base case - rendering the first item in the list
  2. Recursive case - rendering the item, and then the next list

Let's pass [1, 2, 3] to v-for:

We want to grab the first item in the list, the 1, and display it:

Now the component will render out 1, like we expect it to.

But we can't just render out the first value and stop. We need to render out the value, and then render out the rest of the list as well:

Instead of passing in the whole list array, we chop off the first item and pass down that new array. We've already printed out the first item, so no need to keep it around.

This is the sequence of things that are happening here:

  1. We pass [1, 2, 3] into v-for to render
  2. Our v-for component renders 1, and then passes [2, 3] into the next v-for to render
  3. Which takes the [2, 3] and renders the 2, and then passes [3] into the next v-for
  4. This last v-for component then renders out the 3, and we've printed out our list!

The structure of our Vue app looks like this now:

You can see that we have several v-for components all nested within each other!

One last thing we need to do here though. We need to stop the recursion!

Eventually we run out of items to render, so we need to stop our recursion (or we'll get tons of errors and our computer will melt).

Our next step is get slots working.

Recursively nested slots

We've got the basic component working, but we also want it to work with scoped slots, so we can customize how we render each item:

It's not much extra work to get slots in here, so we'll do that right now!

Nesting slots

Once I figured out how to nest slots recursively, it set off this obsession with slots — which I'm still working through 😅

Things like:
👉 Nesting slots n levels deep
👉 Recursive slots
👉 Wrapping a component to turn one slot into multiple
While making sure that each level can provide defaults and override any slot to maintain flexibility, AND transitions work (trickier than you might think 😅)

— Michael Thiessen (@MichaelThiessen) July 19, 2019

If you're looking for some more advanced content on slots, I also did a deep dive into case study that looks at different ways of approaching an architecture problem in Vue with slots.

First we'll take a quick detour into how nested slots work, then we'll show how to incorporate them into our v-for component.

If you know how to use slots normally, I think you'll get this pretty quick.

Let's say we have three components, a Parent, a Child, and a Grandchild. We want to pass some content from the Parent component and have it rendered in the Grandchild.

All we're doing is passing the slot from the Parent to the Child like we normally do, and then again from the Child to the Grandchild.

Starting with the Parent, we pass in some content:

We do some things in our Child component — which we'll get to in just a moment. And then our Grandchild component takes the slot and renders out the content:

So what does this Child component look like?

We need it to take the content from Parent and provide it to the Grandchild component, so we're connecting two different slots together here:

Remember, the <slot /> element renders out content that's passed to the component as a slot. So we're taking that content from Parent and then rendering it inside of the slot of Grandchild.

Adding in our scoped slots

The only thing different with nesting scoped slots is that we also have to pass along the scoped data.

Adding this into our v-for we now get this:

Vue Slots Explained Key

Let's look at our base case first.

If no slot is provided, we default to what's inside of the <slot> element, and render list[0] just like before. But if we do provide a slot, it will render it out and pass the list item to the parent through the slot scope.

The recursive case here is similar. If we pass in a slot to v-for, it will render it within the slot of the next v-for, so we get our nesting. It also grabs the item from the scoped slot and passes that back up the chain too.

And now, we have a component that imitates (more or less) a v-for, but only using the template!

Wrapping up

We went through a long and winding path to get here, but now we've seen how to create a component that replicates a v-for, but only using the template.

We covered:

  • representing lists recursively
  • recursive components
  • nested slots and nested scoped slots (or recursive slots if prefer calling them that)

If you enjoyed this article, please share it with others who may enjoy it as well!

And sign up for my email list below if you want to get advanced Vue content like this every week.

What is Vue.js?

Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.

Games

If you are an experienced frontend developer and want to know how Vue compares to other libraries/frameworks, check out the Comparison with Other Frameworks.

Getting Started

Data

The official guide assumes intermediate level knowledge of HTML, CSS, and JavaScript. If you are totally new to frontend development, it might not be the best idea to jump right into a framework as your first step - grasp the basics then come back! Prior experience with other frameworks helps, but is not required.

The easiest way to try out Vue.js is using the JSFiddle Hello World example. Feel free to open it in another tab and follow along as we go through some basic examples. Or, you can create an index.html file and include Vue with:

The Installation page provides more options of installing Vue. Note: We do not recommend that beginners start with vue-cli, especially if you are not yet familiar with Node.js-based build tools.

Declarative Rendering

At the core of Vue.js is a system that enables us to declaratively render data to the DOM using straightforward template syntax:

{{ message }}

We have already created our very first Vue app! This looks pretty similar to rendering a string template, but Vue has done a lot of work under the hood. The data and the DOM are now linked, and everything is now reactive. How do we know? Open your browser’s JavaScript console (right now, on this page) and set app.message to a different value. You should see the rendered example above update accordingly.

In addition to text interpolation, we can also bind element attributes like this:

Hover your mouse over me for a few seconds to see my dynamically bound title!

Vue Component Slot

Here we are encountering something new. The v-bind attribute you are seeing is called a directive. Directives are prefixed with v- to indicate that they are special attributes provided by Vue, and as you may have guessed, they apply special reactive behavior to the rendered DOM. Here, it is basically saying “keep this element’s title attribute up-to-date with the message property on the Vue instance.”

If you open up your JavaScript console again and enter app2.message = 'some new message', you’ll once again see that the bound HTML - in this case the title attribute - has been updated.

Conditionals and Loops

It’s easy to toggle the presence of an element, too:

Go ahead and enter app3.seen = false in the console. You should see the message disappear.

This example demonstrates that we can bind data to not only text and attributes, but also the structure of the DOM. Moreover, Vue also provides a powerful transition effect system that can automatically apply transition effects when elements are inserted/updated/removed by Vue.

There are quite a few other directives, each with its own special functionality. For example, the v-for directive can be used for displaying a list of items using the data from an Array:

  1. {{ todo.text }}

In the console, enter app4.todos.push({ text: 'New item' }). You should see a new item appended to the list.

Handling User Input

To let users interact with your app, we can use the v-on directive to attach event listeners that invoke methods on our Vue instances:

Note that in this method we update the state of our app without touching the DOM - all DOM manipulations are handled by Vue, and the code you write is focused on the underlying logic.

Vue also provides the v-model directive that makes two-way binding between form input and app state a breeze:

{{ message }}

Composing with Components

The component system is another important concept in Vue, because it’s an abstraction that allows us to build large-scale applications composed of small, self-contained, and often reusable components. If we think about it, almost any type of application interface can be abstracted into a tree of components:

In Vue, a component is essentially a Vue instance with pre-defined options. Registering a component in Vue is straightforward:

Vue Render Slot

Now you can compose it in another component’s template:

But this would render the same text for every todo, which is not super interesting. We should be able to pass data from the parent scope into child components. Let’s modify the component definition to make it accept a prop:

Now we can pass the todo into each repeated component using v-bind:

This is a contrived example, but we have managed to separate our app into two smaller units, and the child is reasonably well-decoupled from the parent via the props interface. We can now further improve our <todo-item> component with more complex template and logic without affecting the parent app.

Vue Slot Name

In a large application, it is necessary to divide the whole app into components to make development manageable. We will talk a lot more about components later in the guide, but here’s an (imaginary) example of what an app’s template might look like with components:

Relation to Custom Elements

You may have noticed that Vue components are very similar to Custom Elements, which are part of the Web Components Spec. That’s because Vue’s component syntax is loosely modeled after the spec. For example, Vue components implement the Slot API and the is special attribute. However, there are a few key differences:

  1. The Web Components Spec is still in draft status, and is not natively implemented in every browser. In comparison, Vue components don’t require any polyfills and work consistently in all supported browsers (IE9 and above). When needed, Vue components can also be wrapped inside a native custom element.

  2. Vue components provide important features that are not available in plain custom elements, most notably cross-component data flow, custom event communication and build tool integrations.

Ready for More?

We’ve briefly introduced the most basic features of Vue.js core - the rest of this guide will cover them and other advanced features with much finer details, so make sure to read through it all!

← InstallationThe Vue Instance →
Hai visto degli errori o vorresti contriuire alla repository? Modifica questa pagina in Github!