Updated code in light of comments 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 components.
In my current project, I have a DataSlicer component that receives a data
prop and performs some manipulation on it. I would then like to pass the manipulated data on to child components via <slot>
. I understand that slot-scope
is the way to do this but it seems to only work when the parent component tells a child to pass something to its own children -- which seems to defeat the whole point (in terms of composition 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 compounded the more I split up my components (for example, if I stick DataSlicer inside another component to abstract the api calls, which are currently handled at the App.vue level, then App.vue also has to also tell that component 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 accomplish what I want using a render function like so:
render: function (createElement) {
return createElement(
'div',
this.$slots.default.map(vNode => {
if (vNode.componentOptions) {
vNode.componentOptions.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 components have to be explicitly written to accept provided props rather than just accepting the prop whether it's provided by a parent component or directly.
I've also tried using dynamic components and v-for (<component>
), but it ends up being really messy as I am essentially re-initializing the already-defined components received in $slots.default as dynamic ones -- it also introduces some recursive weirdness when I want these components to also have children, and it's hard to deal with non-component children.
This is a very common pattern in React and trivial to implement, so I'm still curious if there is another way of accomplishing this that is more in line with the Vue way of doing things.
I am trying to build components that are reusable, self-contained, and composable, so having to specify slot-scope in a <template>
tag in the parent component each time these are used together doesn't really make sense for my purposes. My child components should not be concerned with where their props come from, and my parent components should not be concerned with what props their children are passing on to their own children.
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 component should handle this). – stuff and things