Does defineProps in script setup automatically create a local property of the defined prop? - vue.js

When we pass a prop to a component and define that prop from child component with defineProps a property somehow is created and accessible from child components template.
parentComponent.vue
<template>
<child-component v-model="product">
</template>
<script setup>
import childComponent from "./childComponent.vue"
</script>
childComponent.vue
<template>
{{ product }}
</template>
<script setup>
const props = defineProps(['product'])
</script>
Here in childComponents template, the product can be accessed without needing to use props.product or toRef it. I know that script setup automatically injects the used props but I could not find any info (in docs) that the defineProps does some too. Is there any info about that.

According to this section :
The script is pre-processed and used as the component's setup() function, which means it will be executed for each instance of the component. Top-level bindings in <script setup> are automatically exposed to the template. For more details
Knowing that props are unwrapped directly inside the template and also the refs are used without .value.
If you want to reference some prop inside the script you should use props.product like in this example :
<script setup>
const props = defineProps(['product'])
const total=computed(()=>props.product.quantity*props.product.unity_price))
</script>
if the prop is only accessed by template you could get rid off const props just call the macro defineProps :
<template>
{{ product }}
</template>
<script setup>
defineProps(['product'])
</script>

Related

Vue 3 why is the parent updated when child data changes?

With this parent...
<template>
<h2>Parent</h2>
{{ parent.a }}
{{ parent.b }}
<ChildComponent :data="parent" />
</template>
<script setup>
import { reactive } from 'vue'
import ChildComponent from './components/ChildComponent.vue'
const parent = reactive({ a: 1, b: 2 })
</script>
And this child...
<template>
<h2>Child component</h2>
<p>{{ child.a }}</p>
<p>{{ child.b }}</p>
<input type="text" v-model="child.b" />
</template>
<script setup>
import { reactive } from 'vue'
const props = defineProps(['data'])
const child = reactive(props.data)
child.a = 'why do i update the parent?'
</script>
Why is the data on the parent being updated here? I thought that with binding of the 'data' prop being one-way, I would need an emit to send the data back to the parent? Instead any changes to the child object in the child component is updating the parent object in the parent.
In the documentation it says
When objects and arrays are passed as props, while the child component cannot mutate the prop binding, it will be able to mutate the object or array's nested properties. This is because in JavaScript objects and arrays are passed by reference, and it is unreasonably expensive for Vue to prevent such mutations.
But from my example, a and b aren't nested?
Further reading and I've found that it's the use of reactive on the child that is the issue. It is creating a reactive copy of the original (a reference), so any updates to the copy were affecting both. I needed to use ref instead:
<template>
<div>
<h2>Child component</h2>
<p>{{ a }}</p>
<p>{{ b }}</p>
<input type="text" v-model="b" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps(['data'])
const a = ref(props.data.a)
const b = ref(props.data.b)
</script>
The reason is that JS treats an object like a reference. A more accurate term is call-by-sharing.
So when you modify a props object inside a child component, you actually modify the same object of the parent component regardless you are using reactive or not
Let's consider this code snippet:
<template>
<h2>Child component</h2>
<p>{{ child.a }}</p>
<p>{{ child.b }}</p>
<input type="text" v-model="child.b" />
</template>
<script setup>
import { reactive } from 'vue'
const props = defineProps(['data'])
const child = props.data // <--- No need to use reactive here
child.a = 'why do i update the parent?'
</script>
The data of the parent component will be still updated along with the child component regardless of the use of reactive. So, to avoid side effects, Vue recommended never mutating props directly. You should use event emitter instead
The reason why child component is updating parent values is the two way data binding. You can solve this problem by creating a copy of an object inside a child component and then use the copied version.

Can we extend element plus 'El Tree' component(build with compsition api) to our custom component and use their props?

I am new to vue3, in vue 2 version the "element-ui" uses the "option API" on "ElTree" component so that I can able to extend the 'ElTree' in my custom component and access 'ElTree' props like 'root, dragState'. But now the 'element-plus' library uses the "composition API" so I couldn't able to extend the "ElTree" and access the props 'root, dragState'.
What should I do now?
Can I able to extend the "ElTree" component or do I have to use the 'ElTree' component directly from the 'element-plus'?
Yes.
You can imagine that I want to extend the el-table to create a new component named XTable and use its props.data
//XTable.vue
<template>
<el-table
v-bind="$attrs"
:data="formatedData"
>
<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope || {}" />
</template>
</el-table>
</template>
<script lang="ts" setup>
import { ElTable } from "element-plus";
const props = defineProps({
data: { type: Array<any>, required: true },
});
const formatedData = computed(()=>{
return data.map(...)
})
</script>
Extend component's props: use v-bind="$attrs" to pass all attributes from <xtable> to <el-table>.
Extend component's slots: use <template v-for... to fallthrough slots.
Use their props: define same prop name data for XTable,and pass a formatted one to el-table.Since $attrs object (in Vue3) includes all attributes that not declared by the component's prop or emits options(e.g., class, style, etc), you should manually define in XTable and pass it to el-table

VueJS 3: Access root HTML element in a slot

how do I reliably access the root HTML element in a slot? I tried slots.default()[0].el but its not consistent. I realized if the slot is a simple html, it is not null, which is great, but if it has some vue directives or components, it will be null. So how can I reliably get hold of the root HTML Element in a slot?
I found one possible solution: that is to have the slot content provider to explicitly set the element you want to reference to by providing a slot-prop method to invoke. And also since Vue 3 template supports multiple root elements, its not really reliable to assume that the slot will always have one root element.
Here is the example of the solution:
<template>
<slot :set-element="(el) => element = el"></slot>
</template>
<script setup lang="ts">
import { ref, watchEffect } from "vue";
const element = ref<Element | null>(null);
watchEffect(() => {
console.log(element.value);
});
</script>
In the usage of the component, inject the slot props and use the Function Refs syntax
<MyComponent v-slot="{ setElement }">
<div :ref="(el) => setElement(el)">...</div>
<div>...</div>
</MyComponent>
You could access a slot's root HTML element directly with the slot's vnode property and in your component script, you can using this.$refs.root.
Here is an example:
<template v-slot:[slotName]="{ vnode }">
<div ref="root">
<!-- your slot content goes here -->
</div>
</template>
mounted() {
const root = this.$refs.root as HTMLElement;
console.log(root);
}
EDIT
The official documentation for v-slot can be found here: https://vuejs.org/api/built-in-directives.html#v-slot
Instead, the official documentation for $refs can be found here: https://v3.vuejs.org/guide/composition-api-template-refs.html

pass router params to component object

I have view and I want to load svg based on router params, I have installed vue-loader and it is working if I hard code it.
<template>
<div>
<suit/>
</div>
</template>
<script>
export default {
components:{
suit: ()=>import('../assets/svg/'+Param+'.svg')
}
}
</script>
<style>
</style>
This is what I have. Instead of Param I want to get this.route.params, but when I try this I get undefined which is logical because components wrapper is object. Is there a way to pass a variable here or must I redo the whole thing?
Instead of this.route.params, within a component you should be using this.$route.params. Vue Router Docs.

Passing data from template to component

I have the following, which for my understanding should pass the value of html attribute to the #Prop with the same name however my console.log is always undefined. How is this accomplished?
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
#Component({})
export default class RelayComponent extends Vue {
#Prop([String]) service: string;
constructor() {
super();
console.log(this.service);
...
HTML
<template>
<div service="expecting this value passed"></div>
</template>
<script src="./relay.ts"></script>
Vue props
Vue props are intended to pass data from a parent vue component or instance to a child vue component.
So you have a vue component, you set up a #Prop and then you get the prop for the html of the parent. Should you have a my-parent and my-child components, the my-parent template could be:
<template>
<my-child count="7"></my-child>
</template>
So a child component like this:
<template>
<div class="counter">{{count}}</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
#Component({})
export default class myChild extends Vue {
#Prop() count: number;
}
</script>
Would get 7 as its count prop.
Now, in your case, there is only one component, and you're trying to setup the service variable of the component from the HTML. This is sort of weird because the point of Vue is to achieve declarative rendering from the component data: is the HTML who reacts to data changes, not your component who gets data from the HTML.
(Of course, you can also setup v-model and event listeners to make your components react to user input, but that's another story).
Basically, if I understood correctly what you want to do, your issue is that you're trying to get the service prop from the HTML of the very RelayComponent component.
Instead, you should setup the service prop in the component parent:
// Code of some parent component that renders the RelayComponent
<template>
<relay-component service="this would set the service prop as a string"></relay-component>
</template>
Only, when dealing with objects, you usually don't pass down a plain string, but a javascript object, and a service variable probably is an object, so changes are you're behind something like this:
<template>
<relay-component v-bind:service="serviceVariableInTheParentComponent"></relay-component>
</template>
Where the parent component has a service variable in its data.
 Constructor and lifehooks
Be wary about explicitly calling constructor in vue class components. If you modify the component state in the constructor, you can break the component.
Probably, you should consider to ever use the created() lifecycle hook instead of constructor() in every Vue component.