So I've played a lot with Vue, and now that my app has bee large, I am having doubts about how to organize it.
I understand ponents and that they make sense when you need to re-use them many time on the same page, for example, a "custom select box" ponent, that will likely be needed in many places.
But what about ponents that will only have once instance? Example: a administration dashboard interface that has 3 areas: a sidebar with some navigation, a main area with stuff you can edit, based on what is selected in the navigation, another sidebar with stuff related to the main area. Do all these need to be separate ponents? Because I don't see any benefit of doing that if there is only one instance of each on the page. On the other side, if I stuff all the code in a single "app" ponent, I could simplify some of the code (less variables)
So I've played a lot with Vue, and now that my app has bee large, I am having doubts about how to organize it.
I understand ponents and that they make sense when you need to re-use them many time on the same page, for example, a "custom select box" ponent, that will likely be needed in many places.
But what about ponents that will only have once instance? Example: a administration dashboard interface that has 3 areas: a sidebar with some navigation, a main area with stuff you can edit, based on what is selected in the navigation, another sidebar with stuff related to the main area. Do all these need to be separate ponents? Because I don't see any benefit of doing that if there is only one instance of each on the page. On the other side, if I stuff all the code in a single "app" ponent, I could simplify some of the code (less variables)
Share Improve this question asked Oct 21, 2019 at 13:58 AlexAlex 66.2k185 gold badges460 silver badges651 bronze badges 1- If you're using a tool like webpack, code splitting also means that you can lazy load ponents. This means your app is faster because it'll only load the ponents it needs at the time. – webnoob Commented Nov 20, 2019 at 15:23
4 Answers
Reset to default 6 +150Summary - typical reasons for using ponents:
- Maintainability.
- Rendering performance via ponent boundaries.
- Loading performance via chunking.
If you find that using fewer ponents improves maintainability then that's fine. It may well be the correct design for your application.
Verbose version below.
The primary reason for using ponents is to improve maintainability.
Components that are reused in many places such as a select-box are obviously easier to maintain than repeating the same code over and over. However, it is worth considering why that is. It's not just that duplication makes it more difficult to make a change to all of the select-boxes. The other key benefit of using a ponent is that the extra level of abstraction can reduce mental overhead.
Let's say someone trying to maintain the code sees something like this (pseudo-code):
<select-box :some-prop="blah">
<select-box-option v-for="something" />
</select-box>
Immediately it's clear that this is a select-box
. All the gory implementation details of the select-box
are hidden away and we don't need to worry about them. By looking at the props, child ponents and events we can quickly deduce what data goes back-and-forth between the parent ponent and the select-box
. This is just separation of concerns.
This benefit also applies to ponents that aren't reused. Let's take the sidebar example. We might see this in the template for our main ponent:
<nav-sidebar @navigate="onNavigate" />
The ponent's name allows us to quickly identify it as the sidebar. If our current task doesn't involve the sidebar then we can just skip over that bit of the template. As the code has been moved off to a different file we have no difficulty establishing which bits of the code are part of the sidebar and which bits aren't.
In this example the nav-sidebar
doesn't have any props and only has a single event. From that we can start to draw some conclusions about how these ponents interact. It would seem that the nav-sidebar
doesn't need anything passed from the main ponent, it could quite happily live stand-alone. If we need to debug a problem with data flowing the other way we'd almost certainly start with onNavigate
.
We couldn't start making deductions like these anything like as quickly if everything was mangled together into one, big ponent.
Of course it could be that our deductions are wrong. It could be that the nav-sidebar
does some horrible things involving $parent
to grab data from its parent ponent. However, that just illustrates why using such techniques is considered bad practice. Maintainable code should allow developers to jump to reasonable conclusions based on the abstractions that appear to be in place.
But it is possible to go too far the other way.
A good abstraction allows you to free up some mental capacity by hiding details behind a label. A poor abstraction adds mental overhead by hiding the code you want to see behind some indirection. Add to that the difficulty of naming things and the burden of extra glue code and you may well be better off just ditching the extra layers and keeping everything inline.
The other thing that can go wrong is splitting ponents up in the wrong way. Separating concerns requires clean partitions of those concerns. Chop things up slightly differently and you end up with a single concern being spread across multiple ponents and the resulting mess is typically worse than if you hadn't bothered splitting things up at all.
Vue allows you to split up your JavaScript code in a number of ways, ponents being just one. Separate .js
files, plugins, filters, Vuex, mixins, etc.. There are several options available to you.
Templates, on the other hand, can only really be split up by using ponents. If you want to break a huge template down into more manageable chunks then ponents are really the only way to go.
This brings us to another key reason for using ponents.
A template is piled down into a render
function. When that render
function is run it registers reactive dependencies, just like a puted property. If any of those dependencies changes it will trigger a re-render. That runs the whole render
function again. Even if that doesn't result in any changes to the DOM it will require the generation of all the relevant VNodes and the diffing algorithm will need to check all of them.
Component boundaries are also rendering boundaries. Each ponent makes its own decision about whether or not to render based on whether its dependencies have changed.
So, taking the nav-sidebar
example, let's say something changes in the nav-sidebar
so that it needs a rendering update. If the nav-sidebar
is a separate ponent then it just needs to run the template/render
function for that ponent. If instead we bundle all the nav-sidebar
code into the main template then we'll have to re-render everything.
Vue also has support for lazily loaded ponents as a way to reduce the initial load time of the page. The idea is that many applications have large sections, such as admin interfaces, that aren't relevant to most users. Rather than incurring the overhead of downloading all of those ponents you can split the ponents into chunks and download the chunks when they're needed. This is usually implemented via the Vue router configuration.
Chunking aside, the typical way to use the router is to have separate ponents for the different pages. While in theory it is possible to use the same ponent for all routes that is unlikely to lead to something more maintainable. I would add that the definition of 'page' is a little fuzzy here but in most applications it's clear what constitutes a different page, resulting in a different ponent.
No tome on creating code monoliths would be plete without some mention of testing. Unit testing should be thought of as a form of reuse and a particularly extreme form at that. Tests have an unrelenting knack for exposing the mess of spaghetti that hides behind what you thought was a nice, clean design. I'm not going to pontificate on testing but suffice it to say that you won't be able to write unit tests unless you split things into suitable units.
Another key feature of a ponent is that it has its own set of properties. Its own data
and its own puted properties. This sounds obvious but it gains significance when you consider looping via v-for
.
<div v-for="item in items">...</div>
The example above uses inline elements instead of ponents. Any state can only live on the parent. That state needs to be held for each loop item, so we may end up with multiple arrays holding different aspects of the state. Computed properties are similarly difficult to implement when working with loops. We typically end up using methods instead:
<div v-for="item in items" :class="getClassesFor(item)">...</div>
Now consider the ponent version:
<my-ponent v-for="item in items" :item="item" />
Each my-ponent
can hold its own state. Let's say, for example, that my-ponent
has expand/collapse functionality. That can now be stored in a local data
property within each instance. Likewise each instance will have its own puted properties.
Arguably this is just reuse as the v-for
creates multiple instances. However, given we're specifically introducing a new ponent just to provide a form of property scope, I think it warrants a special mention.
I personally like to have 3 types of ponents.
Reusable system ponent. These are used for generic layouts, custom buttons, custom select box ... They are meant to be reused multiple times in the code and be very versatile.
Page/View ponent. The route usually routes to a specific ponent. This ponent is some kind of assembly of multiple ponents. The distinction allows to quickly identify the "page" of the application.
Logical division. These are the hardest to find. I tend to isolate things that are not related to each other. For instance, a footer may not be reuse but a modification in the footer should concern only the footer. Other examples are the navbar, the menu, each section of an admin. These ponents should be reusable as much as possible but sometimes they will be specific.
Another example: A ment system. A "ment" would be a ponent of type 3. A "ment thread" display would be another ponent of type 3 that use the "ment" ponent. A page would exist for the subject of a ments thread and would be of type 2. Note that each ponent of type 3 and 2 may use other ponents of other types. If I want to change the display arrangement of the ment thread I only have to change the "ment thread" ponent.
Vue ponents uses not only for re-use. You may split ponents into logical blocks. SPA for example. You can create grid based on vue ponents.
Here is the reason why you need to split codes even it is used only once.
With hundreds or thousands of lines of code, it's no question that splitting code in different files can help make it more manageable. Although, this can quickly turn into a plicated mess if the splitting isn't thought out beforehand.
The more you split codes, the clearer it bees.
It does not only work for Vue JS but also works for almost programming languages.