Is overriding a global Vue component safe? - vue.js

Let's say there is a global component BIcon.vue available everywhere.
And another component, but regular not global, called BIconFake.vue.
We can override BIcon.vue by BIconFake.vue like that:
<template>
<div>
<b-icon icon="plus"><!-- <- Here is it BIconFake component! -->
</div>
</template>
<script>
import BIcon from '~/components/BIconFake'
export default {
components: {
BIcon // <- BIconFake component inside!
}
}
</script>
By this way, Vue.js will display BIconFake component instead of regular BIcon component.
I tried to pass props, events or attributes and it works like expected.
Vue.js is awesome... and big. Really, I don't know everything about it, and I don't want to see side effects or unexpected behavior when doing this override.
So, I want to know if it's safe to do that? Does it make a mess in Vue.js instance? What about memory?
we can override component with pure vue.js. Also, I made this example for Buefy, but we can do that with any UI frameworks like Quasar, Vuetify...
Thinking globaly, Is it good to override components of UI frameworks? What about security, scalability and maintenability?
In fact, I searched a way to build a plugins or addons system to my Nuxt.js app, like wordpress plugins.
Is it a good architecture to start building my app by overriding vue component? Is there another way to build app addons for vue, by using npm or webpack?

If you are going to wrap existing components like that then you should keep in mind the Liskov substitution principle. <b-icon-fake> can likely be used in place of <b-icon> provided that it:
accepts the same props
emits the same events
exposes the same public methods (if it is used with a ref)
behaves in the same way
Most of those points probably do not apply for a simple <b-icon> component.
Also keep in mind the template of your wrapped component now includes an extra <div> around it. This can interfere with styling and things like that.
You can eliminate the additional memory overhead by using a functional component instead, but you will need to write the render function manually to preserve the behavior of the wrapped component. But honestly I wouldn't worry too much about memory usage unless you have determined it to be an issue (after profiling your app).
In terms of whether it is "good" to do this or not, I can't say. There are advantages and disadvantages. In my opinion, wrapping components is fine as long as you are the only consumer of the wrapper component and doing so doesn't affect any existing usage of the wrapped component outside of your code.

Related

Pass ref to default slot in renderless component

I am trying to build a renderless component in vue 3 and want to pass a ref to the default slot.
When I am using the h render function I can just pass a ref:
return h('div', {ref: someRef}); // works
If I try to do the same with the default slot, it does not work:
return slots.default({ ref: someRef}) // does not work
return slots.default({ someRef}) // also does not work
Is there any way to do this without wrapping the default slot into another div or similar?
Checked already the documentation and other resources, but couldn't find any solution.
Direct answer
Yes return a function from your setup hook!
setup(_, slots) {
const someRef = ref()
return () => slots.default({ ref: someRef })
}
vue3 docs link
vue3 docs for renderless component pattern
Contextual answer for those in the comment section questioning the renderless/headless pattern
Yes, sometimes you just want a renderless (or headless as the kids these days say) wrapper for functionality (logic), without limiting the HTML that consumers can use.
But for that functionality to work, the renderless/headless component still needs to identify some of the HTML that consumers render into the default slot, in order to provide DOM-related functionality for example.
I have the same struggle when using this pattern, and have been relying on a "hack": passing specific data attributes via slot scope, that consumers need to bind to the desired elements (not only root) and then using good old document.querySelector to grab them
I has served me well in vue2 and I've been using it in production with no problems, but I was wondering if with the new dynamic :ref attribute of vue3, we can do it differently: probably passing a reactive ref variable and then doing a simple assign, and apparently it works.
<renderless #default="{ someRef }">
<some-consumer-comp :ref="(el) => someRef.value = el" />
</renderless>
Here's a sandbox demo old way for vue 2
Here's a sandbox demo new way for vue 3
Do note that if you want to handle edge cases, like handling conditional rendering of slot content, you probably need to do a bit more work and add some watchers.
This pattern is a bit out of fashion these days, with the introduction of vue composables since you can abstract the logic into a useSomeFunctionality and use it directly on the component you want, but it's sill a valid one IMO.
Cheers!

Vue - Is it better to keep all props in one large mixin

I have a component library where i would like to standardize the props, component etc.
Thoughts on combining them props/methods/other mixins/etx into one larger mixin
All property names would be the same
Remove duplicated code on refactoring to adjust components from local props/methods/computed/ to "global"
Not all components would have need for every piece of data contained within the mixin - point 4
Would tree shaking remove the unused code on Rollup?
Is this a good idea?
If your component library is not constrained to using Vue 2 you might want to take a look at Vue Composition API to share functionality (methods + reactive state) between different components.
Mixins might not be what you really want to be using because you kind of lose information as to what features/props/methods really will be put inside your component without re-checking the mixin code. You also might run into issues when component options get merged by Vue at runtime. Check out these posts for more information:
https://css-tricks.com/how-the-vue-composition-api-replaces-vue-mixins/
https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html
As for sharing props: What I've seen just yesterday (but not yet tried!) in a talk by John Leider - creator of Vuetify - is create a function that returns the props you want to reuse between components. Then you just call said function inside your props definition and use the spread operator.

Target and manipulate single DOM element in vue

Somehow I still can't wrap my head around some core vue concepts.
I have made some simple webpage using phalcon. Created it so, that it would work without JS and now is the time to add some bells and whistles - ajax queries and the like, for the user experience to be better.
I wanted to do everything using vue, to see how it all adds up. But after hours of googling I still can't find solution for the simplest of tasks.
Say: I want to get a text paragraph in a series of <li>-s and change it somewhat. Maybe make excerpt of it and add 'see more' button behind it. Now, in jQuery I would just iterate with each() and perform the tasks. With vue targeting set of DOM elements is much harder for me, probably because of whole paradigm being "the other way round".
I know I could iterate with v-for, but these elements are already in the DOM, taken from the database and templated with volt. I had even this wild idea of creating .js files from phalcon, but it would completely negate my strategy of making functional webpage first and then enhance it progressively.
Frankly speaking I feel like I'm overcomplicating for the sake of it, right now. Is vue even fit for a project like this, or is it exclusively a tool to build app from the ground up?
Vue's templating is client-side, which means if you are delivering an already templated html page (by your backend) there is little vue can still do for you. Vue needs data, not DOM elements to build its viewmodels.
This becomes pretty obvious when building a single page application for example, which would be rendered only on the client-side. You'd simply load the data asynchronously from a backend api (REST for example) and then do all the rendering on the client.
As far as I understand your usecase you want to mix client and server side rendering, rendering most of the non-interactable content using your backend's templating engine and adding some interactivity using vue. In this case you'll need to add some vue components (with their own rendering logic) to your backend template and pass data to that component using vue's data-binding.
Here's an example:
...
<div id="app">
<my-vue-list :products="{% products %}"></my-vue-list>
</div>
...
And in your JS:
var app = new Vue({
el: '#app',
data: {
components: {MyVueList} // You will have to register all the components you want to use here
}
})
Vue provides the ref attribute for registering a reference to a dom element or child component:
// accessible via this.$refs.foo
<li ref="foo">...</li>
Do note, however, that refs are not reactive, as stated in the docs:
$refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.

what is the right way or the vuejs way to data bind the entire page?

Coming from the knockoutJs background. If you don't specific the binding to an element. You can use the model to cover the whole page of elements. For example, i can make a div visible if a click event happened. I'm learning VueJs and from the documentation. I see the vue instance required you to speicif an element with el.
like this:
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
what if my button is not in the same div as the '#app' div. How do i communicate between two vue instance or can I use one vue instance to cover more than one element. what's the vuejs way?
It's very common to bind to the first element inside <body>. Vue won't let you bind to body, because there are all sorts of other things that put their event listeners on it.
If you do that, Vue is managing your whole page, and away you go. The docs cover the case where you have more than one Vue instance on a page, but I haven't come across this outside the docs, and I can't think of a good reason off the top of my head. More commonly, you will be constantly chopping bits out of your root Vue instance and refactoring them into "child" components. This is how you keep file sizes manageable and structure your app.
This is where a lot of folk needlessly complicate things, by over-using props to pass stuff to components. When you start refactoring into components, you will have a much easier time if you keep all your state in a store, outside vue, then have your components talk directly to your store. (put the store in the data element of all components). This pattern (MVVM) is fabulous, because many elements of state will end up having more than one representation on screen, and having a "single source of truth", normalized, with minimal relationships between items in the store, radically reduces the complexity and the amount of code for most common purposes. It lets you structure your app state independently of your DOM.
So, to answer your question, Vue instances (and vue components), don't need to (and shouldn't) talk much to each other. When they do need to (third party components and repeated components), you have props and events, refs and method calls (state outside the store), and the $parent and $root properties (usage frowned on!). You can also create an event bus. This page is a really good summary of the options.
Should your store be Flux/Redux? Vuex is the official implementation of the flux/redux pattern for vue. The common joke goes: when you realize you need it, it's too late. If you do decide to leave Vuex for now, don't just put state in Vue components. Use a plain javascript object in window scope. The right way is easier than the wrong way, and when you do transition to Vuex, your job will be much simpler. Your downstream references might be alright as they are.
Good luck. Enjoy the ride.
You usually put the main Vue instance on the first tag inside the body, then build the rest of your site within it. Everything directly inside that instance (not in a nested component) will have access to the same data.
You can then do this in your HTML:
<body>
<div id="#app">
<p v-if="showMessage">{{message}}</p>
<button v-on:click="showMessage = !showMessage"></button>
</div>
</body>
And set your data to something like this:
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
showMessage: true
}
})
If you want to pass data between components later on you'll have to look up how to emit events, use props, or possibly use Vuex if you got Vue running with the Vue-CLI (which I highly recommend).
If you want to reach tags (such as head tags) outside of the main Vue instance, then there are tools for that. For example you could try: https://github.com/ktquez/vue-head
I haven't tested it thought.

Preserve component state with vue-router?

I have a listing/detail use case, where the user can double-click an item in a product list, go to the detail screen to edit and then go back to the listing screen when they're done. I've already done this using the dynamic components technique described here: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components. But now that I'm planning to use vue-router elsewhere in the application, I'd like to refactor this to use routing instead. With my dynamic components technique, I used keep-alive to ensure that when the user switched back to the list view, the same selection was present as before the edit. But it seems to me that with routing the product list component would be re-rendered, which is not what I want.
Now, it looks like router-view can be wrapped in keep-alive, which would solve one problem but introduce lots of others, as I only want that route kept alive, not all of them (and at present I'm just using a single top level router-view). Vue 2.1 has clearly done something to address this by introducing include and exclude parameters for router-view. But I don't really want to do this either, as it seems very clunky to have to declare up front in my main page all the routes which should or shouldn't use keep-alive. It would be much neater to declare whether I want keep-alive at the point I'm configuring the route (i.e., in the routes array). So what's my best option?
You can specify the route you want to keep alive , like:
<keep-alive include="home">
<router-view/>
</keep-alive>
In this case, only home route will be kept alive
Vue 3
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
Exactly as is, you don't need a Component attribute in the App.vue. Also your this'll work even if your components don't have a name property specified.
I had a similar problem and looked at Vuex but decided it would require too much changes/additions in my code to add to the project.
I found this library https://www.npmjs.com/package/vue-save-state which solved the problem for me, keeping the state of 1 component synchronized with localStorage, and it only took a few minutes and a few lines of code (all documented in the Github page of the package).
One solution without localStorage:
import {Component, Provide, Vue} from "vue-property-decorator";
#Component
export default class Counter extends Vue {
#Provide() count = 0
/**
* HERE
*/
beforeDestroy() {
Object.getPrototypeOf(this).constructor.STATE = this;
}
/**
* AND HERE
*/
beforeMount() {
const state = Object.getPrototypeOf(this).constructor.STATE;
Object.entries(state || {})
.filter(([k, v]) => /^[^$_]+$/.test(k) && typeof v !== "function")
.forEach(([k]) => this[k] = state[k]);
}
}
What seems to me is you are looking for some kind of state management. If you have data which is shared by multiple components and you want to render component in different order, but dont want to load data again for each component.
This works like following:
Vue offers a simple state management, but I will recommend to use Vuex which is a standard for state management among vue community.