I recently subscribed to an awesome podcast on web development called Full Stack Radio by Adam Wathan. There was an episode on Anti-patterns in Vue.js which had Chris Fritz from the Vue core team give a lot of great tips on Vue architecture and some mistakes Vue developers often make. I have not included everything that was talked about, so make sure to listen to the full podcast episode for some great conversations and Vue goodness. I'll include a link at the end of this article.
There were various topics talked about in the episode. Here are my notes on them:
State management and events:
For complex states, Vuex is best. People usually use an event bus for managing state, having components subscribe to events. But what ends up happening is a lot of duplicate state being created which gives rise to it's own set of problems when trying to keep everything synchronized. In the event of debugging, there is no way to know where a certain change is coming from.
Vuex offers a lot of options and makes things traceable. There is a separate Vuex tab in the Vue DevTools which is quite useful for debugging as well as prototyping. We can track the different state changes made, change the styles and with HMR(hot module reload), see the changes happen instantly. For small projects, where there are maybe one or two events that components need to communicate between themselves, an event bus works fine.
If you find yourself doing one or more of these things then that's a good indicator that you should start using Vuex to manage the complex states in your project:
- When you keep passing a lot of props to a component
- Have the parent component keep track of the state of a child so that the siblings may interact with each other via the parent
- When the props passed to a component are not used by that component itself but needed for another component nested inside it
Another no-no is using this.$parent
instead of emitting an event to access and manipulate the state of the parent. This might seem trivial since we clearly know what is being changed in the parent by the child component. But if the project is a huge one and especially has been going on for quite some time, it's difficult to keep track of the entire thing and remember why a certain change is happening inside the parent component. It might seem like a good solution and even a reusable one. Even if you don't use it again, other developers (your fellow devs on the job or fellow contributors in case you're working on OSS) might. Anytime this happens, it makes understanding the code difficult and very hard to debug.
Custom events and callback props. Which is better?
Custom events are those when we emit some event and pass a value through it:
this.$emit('modal-closed', this.isClosed);
On the other hand, callback props are normal methods that the parent passes as a prop for the child to execute. This is done instead of passing a value back to the parent like in custom events:
<button @click="onClose">Close Modal</button>
So both of them are used to carry out the same task. In the first case, we passed a value to the parent via an event and the parent then executes a method or does what's needed with that value. In the second case, we passed the method to be executed to the child itself, expecting it to execute the method on behalf of the parent.
So which is better?
There is practically no difference. Like Chris says:
It's stylistic
This means it totally depends on the user. If you've been using one way there is no reason to change it. Though if you haven't really thought about which method to use, you might want to consider using custom events. The custom event system is much better in terms of confidentiality. It keeps the parent from knowing unnecessary information. Since we pass a single value, the parent is only told of necessary details.
When working on component-driven projects, the goal is to work on the component without worrying about other components. By having the child execute a method on behalf of the parent, we not only have to depend on one component executing a method for another component, but we also may not be aware about that particular child executing that method.
This might happen if there are too many components or the particular parent has many children. It might also become confusing after a few months or so and we may find it difficult to remember how that method is being executed by only looking at the parent.
This does not mean that using callback props has any disadvantages. Using custom events feels more Vue-like and is demonstrated in the official Vue documentation.
When is the right time to use watchers? Is computed properties better?
Computed properties and watchers are so similar in what they do, that most new Vue developers are confused and not sure which to pick. Generally, watchers are best used for asynchronous tasks.
If you want to update a state when another state updates, then that calls for computed properties. A simple example would be deriving a fullName
property from firstName
and lastName
.
Using watchers will get tedious since we have to create a watcher for each property that we need to keep track of. Trying to change the state of a property by closely monitoring all the properties it depends on is a lot of work.
In such a situation, computed properties are a boon. All we need to do is give it the properties it depends on. As soon as any of those properties change, it re-evaluates and makes the changes. That property is cached so that it doesn't get needlessly re-evaluated every time unless it has actually changed.
This is the performance benefit of computed properties over regular methods and watchers.
This doesn't mean watchers have no use. There are situations where computed properties do not help us and we need reactivity that methods do not offer. Thus, in such situations, watchers are the best choice.
I encountered a similar situation during a personal project I was working on. I had an array of users, where each user was an object. There were 3 radio buttons and depending on which button was selected, particular users had to be displayed. There were methods that picked the required users to display. It's quite easy to run the methods using a simple click listener (which is how I did it). But if we have to make a choice between computed and watchers, then computed properties would not work in this situation.
A computed property works by deriving results from the change in state of one or more properties and returns a value.
Hence for this, watchers are the most suitable out of the two.
If you want to learn more about using methods, computed properties and watchers, definitely check out this in-depth article by Sarah Drasner.
Reusing code the right way
There are quite a few ways to reuse code in Vue. However there are 3 of them which are widely known and quite popular among developers:
- Using components
- Using directives
- Using mixins
Making use of components to reuse code is the core foundation which Vue is based on. However Chris tells us that many of the open-sourced plugins make use of directives and mixins when they could have been components. Many developers are making use of mixins and directives the wrong way.
A good use case for mixins is when we want components to do something different than their usual specific behavior
Directives are there for sharing behaviors between many different elements. They make more sense on an element rather than make that behavior part of a separate component. Very often we see some behaviors that are quite generic and not specific or unique enough to warrant a separate component for them.
Chris mentions a good example of an autofocus feature. We need to manually manipulate the DOM with it but it won't be used so much that we require a component for it. A directive is the best choice in this situation.
People seem to be using mixins a lot and sometimes even when there is no need for it. Scoped slots provide the same functionality as mixins and are, most of the time, the better choice. The situation where we would absolutely require a mixin is very specific. A scoped slot is more compositional, everything we need is provided by the wrapper component and we can choose what we want to include.
A good use case for mixins is when we have components that do something very specific but depending on the situation we would like them to do something different. We can create a mixin that is a function which returns component options. Thus we have dynamically generated component behavior. For such dynamic behavior we would need some variables as well. Instead of putting them in the component along with the required ones, we can instead put them inside this function.
There are more interesting conversations in the episode and lots more to learn. I recommend listening to the episode at least once to get a better idea of things and then subscribing to this awesome podcast.
You can find the podcast episode here. You can find all of the people mentioned above, on Twitter - Sarah Drasner, Chris Fritz, Adam Wathan. Make sure to follow them so you can stay updated on their work. If you have some questions regarding Vue I'm sure these guys will be more than happy to help. If I missed any good tips that I should've added to this article, let me know in the comments below.
Top comments (14)
So you say
" So both of them are used to carry out the same task. In the first case, we passed a value to the parent via an event and the parent then executes a method or does what's needed with that value. In the second case, we passed the method to be executed to the child itself, expecting it to execute the method on behalf of the parent."
And then you say there's not much difference other than stylistic. This is completely wrong and misleading. By having a child emit and event up that the parent handles, you create a looser coupling. By passing down a method that the parent expects the child to execute, you've now tightly coupled those 2 component.
The difference is, that in the former scenario, the parent only needs to know how to handle an event it expects, and the child only needs to know to emit an event up. In the latter scenario, the child needs to know specifically what method it's expected to execute. Additionally, it now has to also declare a property to receive that method.
Whilst the difference may seem subtle, trust me in terms of tightly vs loosely couple components, and refactoring/boilerplate etc, it definitely makes a substantial difference.
Vue docs very clearly dictate that the coupling pattern of components is data down, events up. Objectively, you're doing your readers and their clients/future developers that will inherit their code a dis-service by going against this pattern.
If you're going to teach, teach the correct principles please.
I'm not sure if you read the article from the start or not, but I'm not teaching anyone anything.
This is all that I got from the Full Stack Radio podcast episode that I linked to. I listened to the podcast episode and wrote what I understood in my own words to share with people who might not have heard about the podcast or prefer reading rather than listening.
Now I'm not saying what Chris Fritz said is wrong. It could be that I misunderstood what he said. I'll listen to it again closely and look at the Vue docs about the coupling pattern you mentioned. I'll make changes to the article when I increase my understanding of it.
Thank you for pointing it out :)
Eh, I don't agree with your needlessly scathing criticism of this.
You are correct, that the Vue docs do teach the concepts in the way you described. You are incorrect that using a callback method as a prop instead of responding to an event is the correct way to do this (see this thread: forum.vuejs.org/t/events-vs-callba...).
Your statement that callback props enforce tightly coupling is wrong. You are assuming that the child component must have information about the parent component's function, and must pass the correct values to that function as a result. Flip this concept on its head - you can design your child components to call whatever callback function is passed with a set of values, in the same way that a child emits a set of values. No real difference - the onus is on the parent component to design its callback / emit-responding function to handle the returned data correctly. We have plenty of JS APIs that work this way - you don't dictate what DOM events return, you respond to the parameters that are being returned.
The advantage that the callback prop method has over the emit method is that callback functions can be required, validated, and defaulted. You cannot require a parent component to respond to an emitted event. You could lose hours of work figuring out silent "failures" where a parent component is not responding to an emitted event.
The advantage that the emit method has over the callback method is that your child component does not need to check for or validate the presence of a prop. Your child component can just emit events. It's the literal inverse of the callback method's advantage.
Some of the things that are recommended in this article are not good. Beware
Um, I'm not sure if you read the beginning of the article, but this is from the podcast episode where Chris Fritz gave these tips. I'm not sure whether a core Vue team member would give incorrect recommendations. It's probably because I didn't interpret them correctly. Let me listen to the podcast episode again and verify it.
heΒ₯Β yes I understand that it's from a vue core team member. I applaud you for being so studious and learning so much. I am not trying to be mean or anything here. But I am just saying that the problems I pointed out were SOLID principles that were created by the pioneers of coding and engineering, not by me. I will take the pioneers' advice any day over some modern day framework creator.
Yes, I understood your intention wasn't to call me out on it. I thought you hadn't listened to the podcast because there are some who don't like listening to podcasts π . After all, that was my original intention behind writing this article, the tips given in that podcast and topics discussed were too good to not be shared. I didn't intend to be arrogant about what I wrote. It is, after all, something I repeated from a different source.
Anyways, I listened to the podcast again carefully and I feel what I wrote was somewhat too general. Although what I wrote is close to what Chris said, there are some words I failed to remember and omitted them. I'll rephrase those 3 points to better reflect what Chris meant when he recommended using Vuex in those situations.
From listening to the episode again, I think the main reason he recommended using Vuex instead of using props and event bus in those situations is that with Vuex, it's easy to keep track of changes in the state since Vue DevTools provides a way to trace the changes in the state.
I agree that using global variables is bad, and using global state is more so. I think even if Vuex is okay to use in a situation, it will be better to only have the components that require the Vuex state to access it.
I am grateful you pointed this out. Otherwise, I wouldn't have paid close attention to this. Listening to the podcast again and reading your comments made me realize I should be careful not to use Vuex just because it looks like a good situation to use it.
Can you give some examples?
here's one for example, per this article: "If you find yourself doing one or more of these things then that's a good indicator that you should start using Vuex to manage the complex states in your project:
When you keep passing a lot of props to a component
Have the parent component keep track of the state of a child so that the siblings may interact with each other via the parent
When the props passed to a component are not used by that component itself but needed for another component nested inside it"
I think of state stored in Vuex as just a global variable, because it is. I think if you do some googling around and reading you will find that global variables should be a last resort to use, they shouldn't be used because they are more convenient (ie. "passing a lot of props to a component", 'having to pass to a grandchild component'), they should be used for when multiple components share a common state and achieving your objective without creating anti-patterns would be otherwise unavoidable.
also, as a design principle, you only want to expose the minimal possible information to each component. (eg. the users component shouldn't have access to the inventory component variables) Storing variables in the global state is exposing every component to that variable.
Hi and thanks for the article. Can I please ask for a code example of the "mixin as a function" pattern you mentioned? I can't find it in official documentation.
Thanks for reading.
Honestly, I haven't used mixins a lot. About using mixins as a way to have dynamically generated component behavior, I have a few examples but can't find a way to implement them.
Chris mentioned in the podcast that mixins have very specific use cases so maybe that's why it's hard to come up with a situation that justifies making use of mixins. I think he must have come across such a use case or something similar to it. I'll try asking him and get back to you.
Thanks. I'm asking because it was explicitly stated in your article that it is possible, and "how to achieve that" was the exact question I was asked just a few days ago by another developer. I thought you actually managed to make it work somehow. I'll try asking Chris too and if I get an answer, I'll paste it here. Thanks a lot. :)
I asked Chris over the weekend. The example he gave was from a situation he came across. I think he even mentioned it in the podcast but I think I forgot about it π .
The situation is that you have some input components that you're using for multiple currency handling. So depending on the currency option selected, a function would dynamically generate a mixin.
I'll try my best to implement it. Once he gives the green signal that the pattern has been properly implemented, I'll post it here.