How to pass props without creating setters and getters? - vuejs2

In the code below I pass an object to a child component. Vue creates a pair of setter/getter for each property in this object. In other words, it binds each property to make the component reactive. Is there a way to pass an object, like I'm doing here, but without binding? In a real life application I pass an object with ten's of properties and a setter/getter pair is created for each also. This impacts performance a bit. What would you recommend?
Vue.component('child', {
template: '<div>Child!</div>',
props: ['params'],
created () {
console.log(this.params)
}
})
var app = new Vue({
el: '#app',
data () {
return {params: {a: 1, b: 2}}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app"><child :params="params"></child></div>

No.
I would be highly surprised if converting your properties into getters/setters, which is at the core of Vue's reactivity, is the cause of a performance issue.
The only way to pass a property would be to expose it to Vue at some point, which means that it will be converted to getter/setters when you expose it. In order pass the object without them, you would need to do something like JSON.stringify the object and JSON.parse it on the other side. Then, as soon as you try to use in in your child (by adding it as a data property for example) it's going to be converted into a reactive object again.

Related

Vue.js deep watching of computed property using other computed property objects

I am aware of deep watching of properties using the handler in the "watch" section, but I am not seeing how to make vue deep watch in a getter/setter computed property.
Essentially I have something like this, of which vue is not able to observe the changes.
How do I tell vue to observe the changes of "someComputedProperty"?
computed: {
someComputedProperty: {
set (value) {
this.someComputedPropertyObject[this.someOtherObject.id] = value;
},
get () {
return this.someComputedPropertyObject[this.someOtherObject.id];
}
}
}
Thanks in advance,
Erion
If someComputedPropertyObject is a Vue computed property, its value won't be made observable by design (if it creates a new object).
Furthermore, does someComputedPropertyObject have the this.someOtherObject.id property defined upfront? If not, you're creating a new property which Vue cannot observe. Use Vue.set (or this.$set) instead.

vue unexpected reactivity from props

I just noticed an unexpected behaviour and now I don't know if it is normal or not.
I have a component named follows and a child component named follow-list-modal
I'm passing a followList (pagination ) from follows to its child component follow-list-modal
In the follow-list-modal I store the paginated array in the variable members
Follows.vue
<template>
<div>
<follow-list-modal
:follow-list="dataset">
</follow-list-modal>
</div>
</template>
<script>
export default {
props: {
dataset: {
type: Object,
default: {},
},
},
}
</script>
FollowListModal.vue
<template>
<div>
<button #click="fetchMore"> More </button>
</div>
</template>
<script>
export default {
props: {
followList: {
type: Object,
default: {},
},
data() {
return {
members: this.followList.data,
dataset: this.followList,
};
},
methods: {
fetchMore() {
let nextPage = parseInt(this.dataset.current_page) + 1;
axios
.get(this.dataset.path + '?page=' + nextPage)
.then(({ data }) => this.refresh(data))
.catch((error) => console.log(error));
}
},
refresh(paginatedCollection) {
this.dataset = paginatedCollection;
this.members = this.members.concat(...paginatedCollection.data);
},
}
When I click the button More in the follow-list-modal to get more data, I then want to append the new data to the members array.
The unexpected behaviour ( for me at least ). is that if I use push in the refresh method
this.members.push(..paginatedCollection.data);
It appends data not only to members but also to followList which is data that comes from the parent component follows
But if I use concat instead of push, it appends data only to members variable and not to followList
this.members = this.members.concat(..paginatedCollection.data);
Is this behaviour normal ?
I don't get why the followList changes when the members variable changes, I thought that reactivity is one way.
In other words, the members changes when the followList changes, but not the other way around
P.S I don't emit any events from follow-list-modal to follows or change the data of the follows component in any way from the follow-list-modal
In JavaScript, the properties of an Object that are also Objects themselves, are passed by reference, and not by value. Or you might say that they are shallow copied.
Thus, in your example, this.members and this.followList.data are pointing to the same variable in memory.
So, if you mutate this.members, it will mutate this.followList.data as well.
You could avoid this by doing a deep copy of the objects. The easiest method, and arguably the fastest, would be to use JSON.parse(JSON.stringify(obj)), but look at this answer for more examples.
data() {
return {
members: [],
dataset: [],
};
},
created() {
this.members = JSON.parse(JSON.stringify(this.followList.data));
this.dataset = JSON.parse(JSON.stringify(this.followList));
}
You instantiate your data with a direct link to the (initially undefined) property of your prop. This property is a complex entity like an Object (Arrays are Objects), and is thus called via reference. Since members references the same thing in memory as followList.data, when you're calling members, it will follow the reference to the same entity as followList.data. This doesn't have to do with Vue2 reactivity, but here's a link nontheless.
push mutates the array it is called on; it will follow the reference through members and change followList.data, updating its value when called through followList as well. Because the data key is not present on instantiation of the component, Vue can't watch it (just like you need to use Vue.set when adding a new key to a data object).
concat returns a new array of merged elements, and then replaces
the reference in members with the new array. Therefore from this point on you'll
no longer mutate followList.data, even with a push, as the reference has changed to a new entity.
When trying to set your initial members and dataset, I suggest using an initialization method that creates a clone of your followList and writes that to dataset, and running this on the created() or mounted() hook of your component lifecycle. Then create a computed property for members, no need to store followList.data thrice and potentially have dataset and members diverge.

Difference in Vue between data() and adding data in created()

Is there a difference between the following? I've seen examples doing both and am unsure why you would choose one over the other.
Vue.component('test', {
data() {
return { myDataA: 10 };
}
//vs
created() {
this.myDataB = 10;
}
}
Variables set in created() on this will not be reactive. In order for them to be reactive, you must define them in the object returned by data().
Example (note how the text does not change in the output):
https://jsfiddle.net/oyf4quyL/
in a component, there are three places where you can define you data:
data properties
computed properties
props
the created property is lifecycle hook in Vue. what this means, is that the Vue will run this function when the component is created. there are also other lifecycle hooks in Vue you can use, like mounted or beforeMount or beforeCreate and etc.
now with this in mind, let's answer your question.
when you define myDataA in data property, Vue will automatically create some "watchers" for this data property, so anytime that you set a new value to myDataA, anywhere that is using it, will be called again. but when you define a property directly on Vue instance (this), you will lose this "watchers" feature. (which by the way is just some getters and setters!)
so as i said, the best way and the correct way to define a data property is on any of the three places that i mentioned, based on your need. (because each of them has different use-cases that the others).

Why must vue component data be a function?

I am reading up on Vue components, and find their explanation of why data needs to be a function somewhat confusing:
The root instance
var vm = new Vue({
el: '#example',
data: {
message: 'here data is a property'
}
})
A component
var vm = new Vue({
el: '#example',
data: function () {
return {
counter: 0
}
}
})
The Vue docs explain this difference by assigning a global counter variable to each component, and then they act surprised that each component shares that same data... Also they don't explain why they already use a function here.
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<div>{{ counter }}</div >',
data: function () {
return data
}
})
Of course the data is shared now
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
When you reference a global object as your data source, it's no surprise that the components don't have their own data. That is also true for root Vue instances that have data as a property.
var mydata = { counter: 0 }
var vm1 = new Vue({
el: '#example1',
data: mydata
})
var vm2 = new Vue({
el: '#example2',
data: mydata
})
So I'm still left with the question why a component can't have a data property?
From my understanding of this, It's to save memory
Many frameworks, such as Angular 2 or, (at times) React, make each instance of a component a separate object. This means that everything each component needs is initialized for every component. Normally though, you really only need to keep a component’s data separate for each initialization. Methods and such stay the same.
Vue avoids that pitfall by having data be a function that returns an object. That allows separate components to have separate internal state without needing to fully re-instantiate the entire component. Methods, computed property definitions, and lifecycle hooks are created and stored only once, and run against every instance of a component.
See this
The data option should always be a function in the context of components which returns a fresh object.
This precaution is made by vue. So whenever you define the object directly in the data option, vue will catch for making the mistake.
Components are never allowed to directly mutate its state. This prevents us from messing up and doing bad things where components do not have their own state.
If this precaution is not made by vue, then you will have a chance to mutate any other components that owns from the component and this would be a security issue.
Example from the documentation:
It’s good to understand why the rules exist though, so let’s cheat.
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data is technically a function, so Vue won't
// complain, but we return the same object
// reference for each component instance
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
Since all three component instances share the same data object, incrementing one counter increments them all! Ouch. Let’s fix this by instead returning a fresh data object:
data: function () {
return {
counter: 0
}
}
Now all our counters each have their own internal state.
It must be a function because otherwhise the data will be shared among all instances of the component, as objects are call by reference rather than call by value. This does not only happen when you reference a global object but also when data is an object itself.
If data is a factory-function that returns an object this object will be created from scratch every time you mount a new instance of the component instead of just passing a reference to the global data.
Because when Vue init data,
function initData(vm){
let data = vm.$options.data
data = vm._data = typeof data === ‘function’ ? getData(data, vm) : data || {}
/*
Because here,data is a reference from vm.$options.data,
if data is an object,
when there are many instances of this Component,
they all use the same `data`
if data is a function, Vue will use method getData( a wrapper to executing data function, adds some error handling)
and return a new object, this object just belongs to current vm you are initializing
*/
……
// observing data
observe(data, true)
}
why Vue forces the data property to be a function is that each instance of a component should have its own data object. If we don’t do that, all instances will be sharing the same object and every time we change something, it will be reflected in all instances.
var vm = new Vue({
el: '#example',
**data: function () {
return {
counter: 0
}**
}

How to Initialize Data Properties with Prop Values

Still a little bit young in VueJS but I'm loving every bit of it. But now, fixated somewhere.
I want to initialize some values in data() using values passed via props. This is so that I can be able to mutate them later on, since it is not recommended to mutate props inside a component. In fact the official docs recommend this property initialization using prop values as shown below:
{
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
I have something like the one below:
<template>
<div class="well">
<!-- Use Prop value directly on the template: works (but of no help in initializing data) -->
Department: {{department.name}}
<!-- Use prop value but gotten via computed property: Works inside the template but not in the initialization -->
Department: {{fetchDepartment.name}}
<!-- Use the array I initialized with the prop value: Does not work -->
Department: {{this_department.name}}
</div>
</template>
<script>
export default {
name: 'test',
props: ['department'],
data() {
return {
this_department: this.department
// below does not work either
//this_department: this.fetchDepartment
}
},
created() {
// shows empty array
console.log(this.department)
},
mounted() {
// shows empty array
console.log(this.department)
},
computed: {
fetchDepartment() {
return this.department
}
}
}
</script>
As seen in the commented sections above, the initialization is not successful. Neither does the value of this.department appear either from the created() or the mounted() hooks. And note, I can see it is defined using the Chrome Vue Devtools. So my question is, how exactly should I initialize data() attributes using props values, or which is the best way of going around this issue?
I know my answer comes in late but it helps me and hopefully someone else coming here. When props' data are async:
// in the parent component
<template>
<child :foo="bar" v-if="bar" />
</template>
That way, you render the component when props are already available making it safer to follow the guide's recommended ways to initialize data value with props as so:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Happy coding!
You CAN modify a prop. Use the '.sync' modifier. I use it frequently because it is convenient and intuitive. This requires emitting an event to update the value on the parent. I am not really sure the warning of how it results in maintenance issues.
Another method I use if I want to modify a value and not update the parent is using Lodash clone. For example (assuming its available on mounted)
mounted(){
this_department = _.clone(this.department)
}
If you consistently want to mutate the prop and have it change with the parent, then use a computed property. However, in most cases you will want to depend on the state of that data within the component and change it using other functions and thus a computed property will not be what you need.
A computed property is the simplest way to provide a mutable version of a prop, but you might not want to lose data when the prop is updated. You could use an explicit watch.
Watchers
While computed properties are more appropriate in most cases, there
are times when a custom watcher is necessary. That’s why Vue provides
a more generic way to react to data changes through the watch option.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.