Updated code in light of ments about slot-scope
Coming from React, I am having a hard time understanding how Vue uses slots and slot-scope to pass props to child ponents.
In my current project, I have a DataSlicer ponent that receives a data
prop and performs some manipulation on it. I would then like to pass the manipulated data on to child ponents via <slot>
. I understand that slot-scope
is the way to do this but it seems to only work when the parent ponent tells a child to pass something to its own children -- which seems to defeat the whole point (in terms of position and separation of concerns).
This is what I have working right now, based on reading How to pass props using slots from parent to child -vuejs
App.vue
<DataSlicer
v-if="data"
y-axis-dimension="Subparameter"
x-axis-dimension="Year"
value-dimension="Total_Registrations"
:filters="{}"
:data="data"
>
<template slot-scope="childProps">
<Chart :title="title" :series-data="childProps.slicedData" />
</template>
</DataSlicer>
DataSlicer.vue
<template>
<div>
<slot :slicedData="slicedData"/>
</div>
</template>
My expectation was that I could define slot-scope
on the <template>
tag in DataSlicer.vue, but that doesn't seem to work.
Am I missing something? I don't understand why App.vue would need to know or care what DataSlicer is passing to its children. The problem is only pounded the more I split up my ponents (for example, if I stick DataSlicer inside another ponent to abstract the api calls, which are currently handled at the App.vue level, then App.vue also has to also tell that ponent to pass data on to its DataSlicer child[ren]).
Maybe I am just stuck in React thinking here and there's a different or better way to do this?
EDIT:
If it helps, I'm able to acplish what I want using a render function like so:
render: function (createElement) {
return createElement(
'div',
this.$slots.default.map(vNode => {
if (vNodeponentOptions) {
vNodeponentOptions.propsData.slicedData = this.slicedData;
}
return vNode;
})
)
}
This feels rather hackish/fragile and also like I'm stretching Vue to do something in the "react way" when there is probably a better approach.
EDIT 2:
After more research and experimentation, I have also tried the provide/inject approach. This works (by using defineObjectProperty to bind a dynamic getter for the property being passed) but it means child ponents have to be explicitly written to accept provided props rather than just accepting the prop whether it's provided by a parent ponent or directly.
I've also tried using dynamic ponents and v-for (<ponent>
), but it ends up being really messy as I am essentially re-initializing the already-defined ponents received in $slots.default as dynamic ones -- it also introduces some recursive weirdness when I want these ponents to also have children, and it's hard to deal with non-ponent children.
This is a very mon pattern in React and trivial to implement, so I'm still curious if there is another way of acplishing this that is more in line with the Vue way of doing things.
I am trying to build ponents that are reusable, self-contained, and posable, so having to specify slot-scope in a <template>
tag in the parent ponent each time these are used together doesn't really make sense for my purposes. My child ponents should not be concerned with where their props e from, and my parent ponents should not be concerned with what props their children are passing on to their own children.
Updated code in light of ments about slot-scope
Coming from React, I am having a hard time understanding how Vue uses slots and slot-scope to pass props to child ponents.
In my current project, I have a DataSlicer ponent that receives a data
prop and performs some manipulation on it. I would then like to pass the manipulated data on to child ponents via <slot>
. I understand that slot-scope
is the way to do this but it seems to only work when the parent ponent tells a child to pass something to its own children -- which seems to defeat the whole point (in terms of position and separation of concerns).
This is what I have working right now, based on reading How to pass props using slots from parent to child -vuejs
App.vue
<DataSlicer
v-if="data"
y-axis-dimension="Subparameter"
x-axis-dimension="Year"
value-dimension="Total_Registrations"
:filters="{}"
:data="data"
>
<template slot-scope="childProps">
<Chart :title="title" :series-data="childProps.slicedData" />
</template>
</DataSlicer>
DataSlicer.vue
<template>
<div>
<slot :slicedData="slicedData"/>
</div>
</template>
My expectation was that I could define slot-scope
on the <template>
tag in DataSlicer.vue, but that doesn't seem to work.
Am I missing something? I don't understand why App.vue would need to know or care what DataSlicer is passing to its children. The problem is only pounded the more I split up my ponents (for example, if I stick DataSlicer inside another ponent to abstract the api calls, which are currently handled at the App.vue level, then App.vue also has to also tell that ponent to pass data on to its DataSlicer child[ren]).
Maybe I am just stuck in React thinking here and there's a different or better way to do this?
EDIT:
If it helps, I'm able to acplish what I want using a render function like so:
render: function (createElement) {
return createElement(
'div',
this.$slots.default.map(vNode => {
if (vNode.ponentOptions) {
vNode.ponentOptions.propsData.slicedData = this.slicedData;
}
return vNode;
})
)
}
This feels rather hackish/fragile and also like I'm stretching Vue to do something in the "react way" when there is probably a better approach.
EDIT 2:
After more research and experimentation, I have also tried the provide/inject approach. This works (by using defineObjectProperty to bind a dynamic getter for the property being passed) but it means child ponents have to be explicitly written to accept provided props rather than just accepting the prop whether it's provided by a parent ponent or directly.
I've also tried using dynamic ponents and v-for (<ponent>
), but it ends up being really messy as I am essentially re-initializing the already-defined ponents received in $slots.default as dynamic ones -- it also introduces some recursive weirdness when I want these ponents to also have children, and it's hard to deal with non-ponent children.
This is a very mon pattern in React and trivial to implement, so I'm still curious if there is another way of acplishing this that is more in line with the Vue way of doing things.
I am trying to build ponents that are reusable, self-contained, and posable, so having to specify slot-scope in a <template>
tag in the parent ponent each time these are used together doesn't really make sense for my purposes. My child ponents should not be concerned with where their props e from, and my parent ponents should not be concerned with what props their children are passing on to their own children.
- In DataSlicer.vue <slot :slicedData="slicedData"/> is defining the property 'slicedData' on the slot scope. In App.vue, <template slot-scope="{ slicedData }"> is assigning the slot scope to a variable named "{ slicedData }". Does this code work at all? – Andrew Castellano Commented Feb 1, 2019 at 13:43
-
@AndrewCastellano Hi Andrew, yes this code works - somewhere I had picked up the idea that object destructuring syntax could be used to pass what I wanted, but now I think I understand that
slot-scope
is just the namespace. I've updated my code to make this clearer (this doesn't affect the underlying question of why the parent ponent should handle this). – stuff and things Commented Feb 1, 2019 at 14:23
1 Answer
Reset to default 4I was finally able to do what I wanted using the new v-slot
scope in Vue 2.6, described here: https://github./vuejs/vue/issues/9306
Part of my issue was that the Vue team sees the "state provider" approach I was going for as an anti-pattern because it's not clear where props are ing from. I see their point and I think the new syntax provides a good balance between being clear and avoiding the excessive boilerplate of <template>
tags wrapping the child ponents. It also simplifies and makes explicit the ability for a ponent to take both provided props and "regular" props without caring where they e from (my problem with provide/inject).
The final result looks something like this:
App.vue
<DataGetter {...other props} v-slot="{ data }">
<DataSlicer {...other props} :data="data" v-slot="{ slicedData }">
<!-- Using provided data prop -->
<Chart :data="slicedData" />
<!-- Data prop explicitly defined -->
<Chart :data="[ 1, 2, 3 ]" />
</DataSlicer>
</DataGetter>
DataGetter
and DataSlicer
render functions:
render(h) {
if (this.$scopedSlots.default) {
return h(
'div',
this.$scopedSlots.default({ data: this.data })
)
}
}
I hope this is helpful for anyone else ing from React. I'll leave this question open in case someone else es along with a better answer.