Home > other >  How to create a copy of the slot with createElement?
How to create a copy of the slot with createElement?

Time:01-29

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

See full html

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>

  •  Tags:  
  • Related