Vue Slots Vs Props
tldr: If your component does not need to be stateful, turning it into a functional component can increase your rendering performance by 70%.
What is a functional component?
Slots can be useful for when you just don’t know what will be needed. For example, you have a select input component. You could create a prop to allow the user to decide on the style of the option label. A prop to toggle bold/italics. A prop to decide on the max length of the option label before adding an ellipsis. Vue.js has slots in order to make components have a redefinable structure, but by themselves they're pretty limited. Sometimes you need some data or state in order to define how a component should render. If you don't know slots, I suggest you learn them first on the Vue.js docs. Scoped slots is an advanced feature in Vue.js that allows you to. The show-select prop will render a checkbox in the default header to toggle all rows, and a checkbox for each default row. You can customize these with the slots header.data-table-select and item.data-table-select respectively. You can also switch between allowing multiple selected rows at the same time or just one with the single-select prop. The child component contains a prop called 'signal'. I would like to be able to change data called 'parentVal' in the parent component so that the children's signal prop is updated with the parent's value. This seems like it should be something simple, but I cannot figure out how to do this using slots: Here is a running example below.
Functional components (not to be confused with Vue’s render functions) is a component which holds no state and no instance.
In more plain English, this means that the component does not support reactivity and cannot make a reference to itself through this
To create a functional component using Vue’s template syntax, you simply add the functional
keyword to the top-level template
tag:
For a component using a render function, you simply set the functional
option to true
:
Accessing component properties
Without the state or instance, you might wonder how you can reference things like data or methods. Fortunately, Vue provides a “context” parameter (often referred to as ctx
) to the underlying render function.
This “context” argument is an object with the following properties:
props
: An object of the provided propschildren
: An array of the VNode childrenslots
: A function returning a slots objectscopedSlots
: (v2.6.0+) An object that exposes passed-in scoped slots. Also exposes normal slots as functions.data
: The entire data object, passed to the component as the 2nd argument of createElementparent
: A reference to the parent componentlisteners
: (v2.3.0+) An object containing parent-registered event listeners. This is an alias to data.oninjections
: (v2.3.0+) if using the inject option, this will contain resolved injections.
Accessing this “context” parameter is pretty straightforward. For example, if we wanted to do something with props, it might look like this:
A quick note on functional components and props:
In a functional
Why are functional components cool?
Functional components can make accessing your components a little more difficult to work with or introduce some complexity, so why go through the hassle?
Speed.
Because functional components do not have a state, they do not require extra initialization for things like Vue’s reactivity system.
Functional components will still react to changes like new props being passed in, but within the component itself, there is no way for it to know when it’s data has changed because it does not maintain its own state.
For those of you that prefer hard numbers, I did a benchmark for rendering 1,000 list items as stateful components vs. functional components, and the while the stateful components took on average 140ms, the functional components took about 40ms.
See for yourself: https://codesandbox.io/s/vue-stateful-vs-functional-yterr
For very small applications, you may not notice much of a difference, but for a large application, you should see significant improvements to the DOM render/update events after implementing functional components.
When would you use functional components?
Functional components are probably not right for many cases. After all, the point of using a JavaScript framework is to build applications that are more reactive. In Vue, you cannot do this without the reactivity system in place.
There are, however, some excellent use cases to sprinkle in functional components:
- A component that is simply presentational, AKA a “dumb” component. For example, buttons, pills, tags, cards, even full pages with just static text like an About page.
- A “higher order component” which is used to wrap markup or basic functionality around another component.
- Any time you find yourself in a loop (v-for), the items in the loop are usually great candidates.
Functional components are not perfect
They don’t inherit attributes as expected
Let’s say we have a fancy link component which wraps a bit of style around an HTML <a>
tag:
When working with a normal component, any HTML attributes provided from a parent component will be passed directly to a child component. Vue does this for us, and it’s a bit of magic how the class
attribute gets resolved:
The output HTML would be (as expected):
This is a perfect opportunity for a functional component, but when we apply the functional
keyword, even if we update all the props as we need to, we do not get the expected output:
In order to fix this, we need to provide the attributes to the element we want. This applies to HTML attributes, styles and classes are slightly different, listeners, and refs:
Calling a function within a template is awkward
This is just a small issue I’ve run into for a pretty specific use case. When using a <template>
tag, and accepting data through a prop, sometimes we want to modify the data inside the template.
With a standard Vue component, this is easy using either methods or computed props. With functional components, we do not have access to methods or computed props.
However, there is still a way to do this using $options
.
Let’s say our component accepts a 'user'
prop which is an object 'firstName'
'lastName'
, and we want to render a template that shows the user’s full name.
In a functional <template>
component, the way we could do this is by providing a method right on our component definition, then use the $options
property provided by Vue to access our special method:
I have not personally run into this other issue, but a community member found that try to nest functional components that have scoped slots, the behavior is not the same as nesting stateful components with scoped slots https://github.com/vuejs/vue-loader/issues/1136.
Another issue I ran into is that functional components cannot have nested component (neither globally or locally registered). This is kind of a big deal, but there is a hacky workaround. The issue was reported in GitHub and the core Vue team admitted it would be a good feature to add, but it’s not likely to be implemented since Vue 3.0 should be out soon, making functional components unnecessary.
Conclusion
If you care about performance or are working on very large applications, consider getting in the practice of creating functional components. The small learning curve is worth the performance gains.
References:
https://vuejs.org/v2/guide/render-function.html#Functional-Components
https://itnext.io/whats-the-deal-with-functional-components-in-vue-js-513a31eb72b0
You keep reading about 'props'.
You've probably been using them too (even if you didn't realize it).
But maybe you aren't completely sure about what they are.
Or how to use them properly and get the most out of them.
I was once in your position, not entirely sure of what I was doing and feeling like I was drowning. But I've learned a lot over the years, and now I want to pass that on to you.
By the time you're finished reading this guide, you'll know everything you need to know about props in order to be a super productive Vue developer.
And when your co-workers ask you how you know so much, just smile and tell them you're awesome 😉
In this guide we'll cover the most important things about props:
- What are props?
- Two main characteristics of props
- How to pass props to other components
- Adding prop types
- Making props required
- Setting default values
Plus many more things throughout!
So, let's get started, shall we?
What are props?
Props are how we pass variables and other information around between different components. This is similar to how in Javascript we can pass variables into functions as arguments:
Here we pass the variable myMessage
into the function as the argument message
. Inside the function we can access that value as message
.
Props work in a very similar way to this. We pass props to another component, and that component can then use that value.
But there are a couple rules you need to know about first.
Two main characteristics of Vue props
There are two specific things to keep in mind when dealing with props:
- Props are passed down the component tree to descendents (not up)
- Props are read-only and cannot be modified (as you may have discovered)
Vue uses one way data flow, meaning that data can only flow from a parent into a child component. You cannot pass data from a child to a parent.
And because that parent component 'owns' that value it passed down, the child can't modify it. If only one component is allowed to change it, then it's easier to track down bugs since we know exactly where to look.
If the reasoning here isn't super clear that's okay. You'll understand how this works as you use Vue more and more.
Vue Slot Or Prop
Just make sure you don't violate those two rules and you'll be golden.
Let's take a look at how we can pass props from one component to another.
Passing props to other components
If you want to pass a value from your component into a child component, it's exactly like adding an HTML attribute:
Looks like regular HTML, right? Not too scary.
The Camera
component will then use the name
and img
props to render itself to the page. It might render something like this:
Here we render the name
prop into the h2
tag, and use the img
prop to set the src
attribute on the img
tag.
But what if we have this information stored in a variable somewhere?
To do that we need to use a slightly different syntax, since instead of passing a string we want to use a Javascript expression:
The line v-bind:name='cameraName'
tells Vue to bind the Javascript expression cameraName
to the prop name
. A Javascript expression is any snippet of Javascript. It could be a variable name like we have here, or something more complicated.
For example, we could use a logical OR
to use a default if we don't have an image for the camera:
More commonly, we use the shorthand for v-bind
which is just :
and reduces clutter in the code:
Because you can put in any arbitrary piece of Javascript, you can do a lot of nifty little things, such as dynamically adding a class name, or implementing a hover.
Adding in our Props
Vue Slots Vs Props For Sale
We're not quite there yet though.
Before this code will actually work, we need to get the Camera
component to actually listen to the props. By default it will just ignore them.
To do this we have to add a props
section to our component definition:
This is the bare minimum to get things working, but it's not recommended you do this. Instead, you should specify the type of the prop as well, using an object:
By switching from an array to an object, we can specify more details of our props, like the type. We can specify other things too, but we'll get to that in a bit.
Why do we want to add types to our props?
In Vue, props can be many different things. They can be:
- Strings
- Numbers
- Boolean (true or false)
- Arrays
- Objects
By adding in prop types like this we can set expectations for what we'll receive. If we set our camera's name
prop to true
it won't work properly, so Vue will warn us that we're using it wrong.
We'll add a rating to our Camera
component so we can see what using a number looks like. First we add it to the prop types:
Then we'll also update our template so we display the rating on the page:
All we need to do is specify the prop name, no this
is required. In a Vue template everything that can be accessed off of this
is automatically included.
Now we can pass in a number as a prop:
Notice how we used the v-bind
shorthand for this one. If we didn't do this it would get passed in as a String, which is the incorrect type.
Passing in an array or object or any other type works in the same way, using v-bind
or it's shorthand.
Required props
Not all props are created equal.
Some of them are necessary for the component to work correctly.
Others are just extras, giving additional functionality if you need it.
For our Camera
component, we definitely need a name
, otherwise it doesn't make any sense. But we don't need an image, and we don't really need a rating either.
We can specify which props are required and which ones aren't in our prop definition:
Here we set our name
prop to be required by adding required: true
to its prop definition.
By default props are not required, so we don't need to touch the other ones. We could add required: false
if we wanted to.
Default Values
Many times we'll want to set a default value on our optional props, since they may not be provided each time.
Earlier we saw how to add a default image if we didn't have one for our camera, but this is a better way:
Perfect!
Instead of cluttering up our template, we put the default value right alongside all of the other information about the img
prop.
While we're at it, we should specify a default for our rating
prop as well. Right now if it isn't set we'll just get undefined
, which could cause some issues for us:
Now if the camera hasn't been rated, we'll get a 0
displayed instead of undefined
.
Specifying types, adding defaults, and marking props as required are only a couple of the things you can do with prop types. I wrote an article with some killer tips on getting the most out of them.
Using props outside of the template
While being able to use props inside of your template is great, the real power comes from using them in your methods, computed props, and other Javascript used in your component.
In our templates we saw that we just needed the prop name, like this: {{ rating }}
. However, everywhere else in our Vue component we'll need to access our props using this.rating
.
Let's refactor the app so that we use a standard URL structure for our images. This way we don't have to pass it to the Camera
component each time, and we can just figure it out from the name.
We'll use this structure: ./images/cameras/${cameraName}.jpg
So if the camera is the Sony A6400
, the URL will become ./images/cameras/Sony%20A6400.jpg
. The %20
is from encoding the space character so we can use it in a URL.
First we'll remove the img
prop that we no longer need:
Then we'll add a computed property that will generate the image URL for us:
Not all characters can be used in URLs, so encodeURIComponent
will convert those for us.
Because we can access this computed prop in the same way as regular props, we don't need to change our template at all, and it can stay as we had it before:
In this way you can use the component's props in:
- Watchers
- Lifecycle hooks
- Methods
- Computed props
And anywhere else in the component definition!
Conclusion
By now you should know everything you need to know about props in order to be a highly productive Vue developer.
Vue Template Slot
However, there are always more things to learn. Vue (and software development in general) is a never ending learning process.
In order to keep learning more about Vue, and to learn things as I figure them out myself, join my email list below for weekly updates!