I need pass the main component to a deeper child. Currently I am doing on child this.$parent.$parent, but is not reliable for me, because this same child could be in a third+ deepth level.
My idea is transfer the main component reference between childs, like:
<v-some-child :main-component="_self"></v-some-child>
So I can use the main component instance where I think need by passing the _self or this.mainComponent data to a more inner child.
My doubt is:
_self attribute is reliable to points to component Vue instance? Seems that _ prefix is to internal attributes, and $ for public (reliable). If it is true, what I can do use?
There are some better way to do what I wants?
Thanks!
Edit 1: as an immediate more reliable workaround, I am using data() { self: this }, then I am using :main-component="self".
I'm unclear where _self is getting set in your case. Typically _self = this; is an approach used by developers to pass the the current Vue instance via closure to a callback function. I wouldn't trust a _self unless I knew exactly where it was getting set.
Your edit1 should absolutely work. As long as data(){ self: this } is happening on the main Vue instance and in the components you use self when setting a property via a : like :main-component="self" then that should work fine given that the main Vue component data properties are merged into all components used by that vue instance.
It's kinda a cleaver approach frankly. As #raina77ow mentioned it may make sense to look at VueX or an EventBus but the approach you propose in Edit1 should be a reliable way to pass around the main Vue instance.
As an aside, I wonder if root or main might be a better name then self. Given that it's the "self" of the main or root level Vue instance not the the "self" of the child passing the value.
Related
I created a vue component based on MapBox, which is restricted in initializations before it costs money and that is perfectly fine. But I want to reduce reinitializations of my map component for their and my sake.
That's why I thought if it is possible to define the component once, pass in some properties and then handle the state via vuex.
Right now, I'd have to import my component and add the data like this:
<Map
:sources="geoData.sources"
:layers="geoData.layers"
:mapOptions="mapOptions"
:componentOptions="{ drawingEnabled: toggleMapDrawing, activeLayers: activeMapLayers, activeMarkerGroups: [] }"
#loaded="onMapLoaded" #selectedMarkers="onSelectedObjects"/>
The componentOptions are being watched, so the component changes its state accordingly.
My ideas/approaches so far were the following:
I thought about adding the snippet above to the root vue file, but that won't help since I want to place the map component dynamically and not statically before the rest of the page content.
Passing a rendered vue component into a variable and appending that later would be a bit too hacky, if it is even possible.
Using slots, but from what I've seen in the docs, it's not possible to use a slotted component from a parent component in a child like this.
The best idea that has come to my mind was to define the actual MapBox variable (which I suppose triggers the API for initialization) and then save that globally using the store or something. But since that will immediately append the component to a DOM element that will be specified in the options, so I'd have to store that somehow, too.
The initialization of the map happens in the mounted hook of the component and looks like this:
const baseOptions = {
accessToken: process.env.MAPBOX_TOKEN,
container: 'map',
style: process.env.MAPBOX_STYLE_URL,
minZoom: 10,
maxZoom: 20,
zoom: 13,
bearing: 150,
pitch: 50
}
this.map = new mapboxgl.Map(Object.assign(baseOptions, this.mapOptions))
if (!this.map) { throw new Error('Could not create map. Make sure the token is valid.') }
I might be wrong, maybe there's a better way or maybe this whole idea might be garbage, but hopefully it's not. Also please note that I'm not using the vue-mapbox module, because it's not being maintained anymore.
I'm thankful for any ideas and hints :)
You may use <KeepAlive>, a built-in component available in both Vue2 (docs) and Vue3 (docs).
Basically it ensures that a component tagged with keep-alive will only be mounted once. So in your case, you can place the map wherever you want, and the Map will only be initialized once in its mounted hook.
If you need to utilize the moment that your Map gets "focused" or "activated" so to say, then you can utilize the activated and deactivated hooks.
Why you cannot use KeepAlive.
There is an obvious and logical limitation. As long as the parent is alive and mounted, the component's children that are being kept-alive will stay alive. But if the keep-alive component's parent gets unmounted, then all the children will be unmounted aswell even if they were being kept alive. This is all very obvious but I just felt like pointing it out.
Solution
So, in your use case, you want a component (the <Map> component) to be globally kept-alive after its first initialization. I suggest you cache the map element itself and store it in the store. Then on every <Map> component onBeforeMount (Composition API) or beforeMount (Options API) hook, manually check if the element is cached, if it is then insert the cached map from the store, otherwise initialize the map.
Seems like a simple requirement, and of course I could maintain my own flag using the lifecycle hooks. See also https://forum.vuejs.org/t/check-if-mounted-was-called/88177/6 for a similar question.
However, I prefer to use something already build-in. I am sure, VueJs maintains the lifecycle state somewhere.
Here's my watcher, simplified:
#Watch('openId')
private onOpenIdChanged() {
this.submitSomething(); //But ONLY if it's mounted!
}
How can I access the lifecycle state of the same component in a component's watcher, without using my own flag?
There is no public API for this, so the only correct answer is to maintain your own flag.
There is a private property on the component instance _isMounted which Vue uses internally (as of version 2.6.11).
Imagine you have a huge list of things to do:
[
{
id: 1,
name: Pet a cat,
priority: 'extreme'
},
...
]
The app has three components grandparent -> parent -> child
The todo list is declared in grandparent's data function. This is where the list becomes reactive first. The list is then passed from grandparent to parent and from parent to child as a prop. From my limited understanding each component in the chain adds their own getters and setters. My concern is that this way of passing props is not optimal for performance. I mean, should I restructure my code in a way that minimizes such props passing or not?
First of all, Props in Vue are readonly. You'd get a runtime error if you ever try to update one. So actually there's no setters for parent and child components, only for grand-parent.
If your child components wants to update it, you'll have to send events until some component can actually update the data.
Second, you won't have any performance issue with it, it's the way Vue works and it's good at it. Actually it's the proper and most straightforward way to achieve what you want. Obviously, if the parent / child list extends even more, it's gonna be a pain for you to use only props + events, but no performance issue I think.
The other solution to avoid data to be passed through each component descendent is to use a "Vuex Store". This is not super easy to set up and understand for beginners though. You may give it a try if your app is becoming more complexe.
I'd suggest you to stick with your current solution as it has nothing wrong.
Happy coding :)
I thought that two way data flow was not just discouraged but impossible between parent and child components in Vue.js. However I've discovered it's actually possible using a custom class as a prop.
Custom class:
class MyClass {
constructor(val) {
this.val = val;
}
}
Parent template:
<div>
<child :obj="obj"></child>
</div>
Child props:
props: {
obj: MyClass
}
Child template:
<div>
<button #click='obj.val="changed"'>Change val</button>
</div>
Here is a working example: https://codepen.io/francoisgaudin/pen/XWdBOxN
So now I'm wondering why this is possible - is it deliberately allowed or is it a loophole that should not be exploited??
I found the answer in Vue.js documentation on the "one way data flow" (https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow). They specify the following caveat:
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect parent state.
So one way data flow is not strictly enforced and there is no error or warning in this case. This is a loophole - (which is best avoided).
You're able to modify the object because object references are values in Javascript - which makes objects act like they are passed by reference.
In other words:
Javascript always passes by value, but for arrays and objects the value is a reference to the object.
https://medium.com/nodesimplified/javascript-pass-by-value-and-pass-by-reference-in-javascript-fcf10305aa9c
It's allowed because the alternative is copying the whole object (which could include nested objects and is not really practical).
Also, this is an opinion, but you should not do this.
I do not see a downside of using it, more than loosing the track where the object is modified in a very nested child components. I will play around a bit more with your codepen, I also wanna know why it is possible, and Avoid mutating a prop directly error is not raised.
you can use Vue's $emit feature to communicate between child/parent, more examples here :
https://v2.vuejs.org/v2/guide/components-custom-events.html
I'm losing track of reactivity overhead best practices in a Vue 2 Component. I need to generate a one time string with genId() and assign it to the component' id attribute. It seems like overkill to have any watching going on after that.
Is :id="myID" the right way to insert this into the html id?
And then when setting up the source where do I put the non-reactive data? I had 3 thoughts:
Add property myID: genId() to data. But doesn't that add it to the watchlist automatically even though it won't change? Causing overhead?
I read on Vue Forum from a year old answer that myID: genId() should go in the created hook. Are using hooks for this kind of thing a best practice? I thought hooks were discouraged.
Or I could put it in the component methods and then call it directly with :id="genId()
Is there a Vue way to do this?
Use method 2 for non-reactive data (and you use that component many many times on the page that the small overhead of adding the change listeners has any impact.)
created() {
this.myId = genId()
}
The 3 methods behave differently:
Method 1: data
This will call the genId() when the data object is created and add change listeners.
Method 2: created hook
This will call the genId() when the component object is created and doesn't add change listeners.
Method 3: method
This will call the genId() every time the template re-renders. Which happens every time a change is detected on a variable the view is listening to or a $forceUpdate() is called.
Ps. Vue objects already have a unique id: this._uid
but this is a private property and might change in a future version of Vue.