How to access name of the component of the slot?
I want to create a copy of the component provided in the slot:
const child1 = slot
const child2 = h(???, slot.props)
So that child1
renders exactly as child2
.
I need this, so that I can change properties of that VNode, for examples classes.
Context
import { h } from 'vue';
export default {
setup(props, { slots }) {
const children = [];
for (const slot of slots.default()) {
const child = h(???, slot.props)
children.push(h('div', [child]));
}
return () =>
h('div', children);
},
};
Background
I want to make a component similar to q-button-group:
I need 2 components TButton
and TButtonGroup
so that I can style TButton
independently and create groups just by putting those buttons inside the TButtonGroup
.
Example
<TButtonGroup>
<TButton label="Two" />
<TButton label="Three" />
</TButtonGroup>
TButton
should have a different list of classes:
- when it's inside TButtonGroup:
px-4 py-2
- when it's not:
border rounded-lg px-4 py-2
Playground
https://stackblitz.com/edit/vue3-button-group-razbakov?file=src/components/GroupRender.js
CodePudding user response:
Component name of vnode won't tell much, components are already resolved at this point. VNode's element or component is stored in type
property.
The problem with your approach is that render function is an alternative to component template, not a way to access the entire DOM element hierarchy. There will be no TButton
child elements like div
in render function, only TButton
vnode itself. It needs to be rendered in order to access its children.
If TButton
were someone else's component which initial behaviour needs to be modified, this could be done by adding some directive to it and accessing component's children elements.
But since TButton
is your own component that can be modified to your needs, the most straightforward way is to make it change classes depending on a prop and provide this prop when it's inside TGroup
, i.e.:
const child = h(slot.type, {...slot.props, group: true}, slot.children);
children.push(child);
CodePudding user response:
Use the component type you created:
const { h } = Vue
const TBtn = {
props: {
staticClass: {
type: Array,
default: () => [],
},
},
template: `
<div
>
<slot></slot>
</div>
`
}
const TBtnGroup = {
setup(props, {
slots
}) {
const children = [...slots.default()]
.map(slot => h(slot.type, {
class: ['border', 'rounded-lg']
}, slot))
return () => h('div', {
class: ['d-flex']
}, children)
},
}
const App = {
template: `
<t-btn>OUTSIDE 1</t-btn>
<t-btn>OUTSIDE 2</t-btn>
<br />
<t-btn-group>
<t-btn>INSIDE 1</t-btn>
<t-btn>INSIDE 2</t-btn>
<t-btn>INSIDE 3</t-btn>
</t-btn-group>
`
}
const app = Vue.createApp(App)
app.component('TBtn', TBtn)
app.component('TBtnGroup', TBtnGroup)
app.mount('#app')
.px-4 {
padding-left: 16px;
padding-right: 16px;
}
.py-2 {
padding-top: 8px;
padding-bottom: 8px;
}
.border {
border: 1px solid black;
}
.rounded-lg {
border-radius: 8px;
}
.d-flex {
display: flex;
gap: 8px;
}
.t-btn:hover {
cursor: pointer;
background-color: black;
color: white;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app"></div>