I just found out that instead of defining the properties of a component like so:
const props = defineProps({
id: Number,
title: String,
name: String,
})
I can do this instead:
defineProps([
'id',
'title',
'name',
])
This doesn't seem to require type declaration but is there any downside to doing this? Does vue determine the type of each property by itself?
I'm using script setup.
Downside is of course less safety.
Does vue determine the type of each property by itself? No
When providing array of strings, Vue does not validate the type of passed props at all so in case of incorrect usage (which is much more likely to happen as other devs/future you are not able to tell what should be passed in without reading the rest of component's code) you end up with some runtime error somewhere in your component instead of clean error/warning about wrong value passed as prop (or a sensible error from your IDE)
You should use as much specific props definition as possible most of the time.
Well it's not just type declaration.
It is a prop validation feature.
The complete syntax is
const props = defineProps({
name: String,
id: [ Number, String ],
style: {
type: Object,
default: ()=>{
color: "red",
bg-color: "white"
},
validator: function (value) {
return ['red', 'blue', 'green'].includes(value.color)
}
},
})
So the downside of using just the named props is:
No type safety. But even in case of typed props it will only show console warning in development build.
Whereas the advantage of using prop definition is
Multiple types for single prop
Default value for a prop
Custom validator function
Related
I have a component that defines a prop "actionOptions" of type Object. Also, this prop defines a default() function that returns an object with default values.
actionOptions: {
type: Object,
default: () => ({
columnLabel: 'Actions',
}),
},
Inside mounted() I try to print this prop: console.log(this.actionProps) the result in the Dev Tools is:
{
columnLabel: 'Actions',
}
With a warning of:
Invalid prop: type check failed for prop "actionOptions". Expected Object, got String with value "{
columnLabel: 'Actions',
}"
For some reason, Vue is returning the default function results as a string rather than executing it.
I am using vue v^2.6.11. I tried an example on CodeSandbox.io and it works fine there.
Appreciate your assistance.
You haven't shared the code where you are calling this component in the parent, but this error is most likely happening there. I assume currently, your code in the parent looks like this:
<ChildComponent action-options="{columnLabel: 'Actions'}" />
This means that the prop is being passed as a string. If you want to send it as an object you should change it to:
<ChildComponent :action-options="{columnLabel: 'Actions'}" />
This way Vue knows it's a JavaScript object and not a string. Read More
If this doesn't solve the problem, please share the other parts of your both components.
I’m trying to figure out how to use the reactivity system economically. I understand that $options for a component are for non-reactive data and are typically set in a created() or mounted() method from within itself. But is there a way to pass in non-reactive props on init from the parent?
For a simple example:
let’s say I have a component that renders a value as currency.
99% of the time currency will be USD and
100% of the time currency will not change after instantiation.
But the value needs to be reactive.
(this is a dumbed down example just to demonstrate the concept)
props: {
value: {
type: Number,
required: true,
},
currency: {
type: String,
default: 'USD',
},
},
But I don’t want to spend a watcher on currency, since it will never change.
Can I somehow pass it in as an unwatched $option? Is there another way of doing this? Or are watchers so cheap I shouldn't worry about efficiency at scale?
Thank you for any help!
To get a non-reactive field, use a regular HTML attribute instead of a declared property, and access it via the component $attrs object.
I would go with the Vue property though. It is typesafe, validated, and the overhead is insignificant.
<my-element currency='12' />
const currency = parseFloat(this.$attrs.currency)
Why nested fields of literal objects bound to a component’s property do not get reactive and observed?
Example:
<my-comp :my-prop="{ my: { literal: { object: { with: { nested: { field: ‘not reactive’ }}}}}}"></my-prop>
When you do this inside my-comp:
created() {
console.log(this); // you can see in the Chrome console that nested fields of this.myProp do not get reactive (i.e. no getters)
}
I know that I can play around it, but I am just asking why not?
Is there a neat way to make it reactive?
Thank you!
This is because the way "HTML" templates are rendered.
When Vue "compiles" your html, it basicly creates the following "computed" function
render(createElement) { /* `createElement` is also known as `h` in the Vue world */
return createElement('my-comp', {
props: {
myProp: { my: { literal: { object: { with: { nested: { field: ‘not reactive’ }}}}}},
},
);
},
Since the full flow of the value of myProp never passes through the data function, nor through the return result of a normal computed function, it never gets marked for reactivity.
While it might sound weird that Vue marks the return result of all "computed" functions, expect the special render function reactive, it actually makes sense if looking at the design idea of Vue, namely props in, and events out. This not marking of reactivity gives us a performance boost here, compared to marking it reactive.
If you still require it to be reactive (and thus want to go against the core principles of Vue (and thus making your application harder to understand) ), you can do it, by storing the value inside the data section of your component, or by returning it from a computed property.
I'm using computed to copy my prop value and use/mutate it in my component:
export default {
props: ['propOffer'],
computed: {
offer: {
get: function () {
return JSON.parse(JSON.stringify(this.propOffer))
},
set: function () {
this.offer
}
},
}
The problem is within using setter. It is not reactive. When I use some kind of input, there is a delay, so my computed offer isn't updating instantly. Example of input:
<v-text-field
label="Offer title"
v-model="offer.title"
></v-text-field>
This is far opposite to the behaviour when I declare offer as a variable (wthout computed) - then I got my {{offer}} changes instantly inside the <template>
How can I improve it? Am I setting my computed wrong?
To better understand this situation, this is what happens at the moment:
When the application loads, the initial state is:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: undefined
At the point in time, your application will load the v-text-field, this references field offer, and this inits the offer computed variable:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: [Javascript object 1]
[Javascript object 1]
title: "test"
<v-text-field>
value: "test"
As the user types into the v-text-field, its value changes, because the v-model emits back updates:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: [Javascript object 1]
[Javascript object 1]
title: "test123"
<v-text-field>
value: "test123"
As you can see here, the setter is never invoked in the normal operation, and hence your code to save it does not run.
You can solve this by making another computed prop for the title of the offer, and then adding some code to prevent your changes from being made undone.
Let's start with the getter & setter for the title:
computed: {
title: {
get() {
return this.offer.title;
},
set(title) {
this.offer = {...this.offer, title};
}
},
// ....
Now we need to properly handle this set operation inside our main offer function, because if we don't handle it, and basically modify its returned object, we get into the territory of undefined behaviour, as the value of the computation doesn't match the computation.
// ...
offer: {
get: function () {
if (this.modifiedOffer) {
return this.modifiedOffer;
}
return JSON.parse(JSON.stringify(this.propOffer))
},
set: function (offer) {
this.modifiedOffer = offer;
}
},
},
data() {
return: {
modifiedOffer: undefined,
};
},
After doing this pattern, you now have a stable application, that shows no undefined behaviour, for more functionality, you basicly need to check if the propOffer changes, and either forcefully delete the this.modifiedOffer, or add more logic to a different computed variable that informs the user there is a data conflict, and ask him to overwrite his data.
I've seen props being define like:
props: ['message', 'name', 'something']
and
props: {
imperfectNumber: {
type: Number,
required: true
}
}
Whats the difference and how can I define some props that require no validation and others that do all in the same block?
Once you go down the object definition route, you have to do that with all the props. You can't mix them.
You can find more information about the props here: https://v2.vuejs.org/v2/guide/components.html#Prop-Validation
Note:
Instead of defining the props as an array of strings, you can use an
object with validation requirements
I've highlighted the part in the manual that explains. This indicates you can do one or the other.