Do you want to build a codebase that looks like a teenagers bedroom or do you want to build one that looks like its straight out of IKEA.
The difference in clean, predictable and quick code and code that is inconsistent, buggy and takes centuries to add features to boils into a couple of components.
The component we’re talking about today comes in the form of design system components, mainly wrapper components. As a Vue / Frontend developer — you NEED to work with your designer buddy to make sure the design system being built is concise and “inheritable”.
Inheritable? Yeah — inheritable, i.e design pattern that builds on top of itself, evolving design, rather that components that look like they belong to different projects — this is exactly how we built https://flowyse.com
Alright — enough fluff, how do we build a strong wrapper component structure? Let’s use in our example an input field and a button.
Solution architecture questions:
- What states does the input field have ?
- Where is data being fetched and sent
- Will it be maintaining it’s own data layer or absorbing from a parent
- Do I directly sync all my input fields straight to a central management store like Vuex ?
- Hover, OnFocus, Error
- Parent component
- No it won’t, data layer is from the parent (in this instance, you can do it your way)
- No, we’re syncing all data to the parent of the input Great now thats done, lets look at a very simple input component.
- We have a root label wrapping the input component
- We bind the input component to $attrs (more on this very soon)
- We listen to “$listeners” (whatever that is)
- Lastly we listen to on “input” changes and emit “change” event to the parent
VueJS knew that wrapper components were idealistic and natural of every component driven framework. A part of the “prop” proposition is that attributes or “directives” passed down from the parent not recognised as props by the child automatically get attached to the root of the child…confusing
Imagine passing greeting into the input wrapper, if greeting wasn’t registered as a prop — it would be added as a field onto the wrapper of our input field, by setting inheritAttrs to false we prevent that, and instead override that and pass all the meta data directly into the input component.
This way you don’t need to register EVERY prop in the input wrapper, but essentially it passes itself through the wrapper naturally (as a wrapper should allow)
Secondly we have this thing called $listeners — this is Vue’s great way of basically bubbling all events the component listens to up to the parent, this way you don’t need to register each event manually — again, a great way to create a wrapper component.
The goal of the wrapper is to essentially provide some design conformity — PS wrappers should 100% be responsible for custom logic as well, you can easily add as much custom logic to a wrapper component as needed, you can get access to the value of the input field by registering value as a prop.
What we’re left with is an input wrapper with the model defined in the scope of the component where the wrapper is created, and attributes that are meant to be passed directly to the input component will be register as expected.
This is exactly how the entire Flowyse project is built out, and one of the reasons why I could ship it out in 5.5 weeks.
With Vue 3.0 you don’t need so much config when creating your base wrapper component
Our component gets MUCH more simpler to use
Notice we no longer have $listeners or inheritAttrs
In Vue 3.0 there’s no more automatic inheritance of attributes, which means $attrs automatically includes all non-prop related attributes without needing to define inheritAttrs: false .
Even with the listeners v-on compiles straight to the attributes @enter compiles to on-enter . By simply doing v-bind="attrs".
"attrs" include all non-emitted listeners as well, and the best part is….
v-model compiles into model-value and on-model-update by doing v-bind="$attrs" so no more model option nor do we need to override the native input event like we did before