I have following components:
Child.vue
<template>
<li>
{{ idx }}
</li>
</template>
<script lang="ts">
import Vue, { defineComponent } from 'vue';
export default defineComponent({
data () {
return {
idx: 0
}
}
});
</script>
Parent.vue
<template>
<nav>
<slot />
</nav>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
mounted() {
//
const slots = (this.$scopedSlots.default ? this.$scopedSlots.default(this.$attrs) : []);
for (let i = 0; i < slots!.length; i ) {
const s = slots![i];
// Essential magic must happen here, but it does not :(
if (s.data?.attrs) slots![i].data!.attrs!.idx = i;
}
},
});
</script>
Controls.vue
<template>
<Parent>
<Child />
<Child />
<Child />
</Parent>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Parent from './Parent.vue';
import Child from './Child.vue';
export default defineComponent({
components: { Parent, Child }
});
</script>
So as the outcome I wish every Child to know its position or index, but what I get is 000
,
so setting data on child component doesn't work for some reason.
Any hint on improving typescript code are also welcome.
Here is codepen in js: https://codesandbox.io/s/codepen-vue-forked-focjc1
CodePudding user response:
You're trying to break the governing principle of slots.
They were created as a mechanism to transfer template responsibility from the slot bearer component (in our example: <Parent />
) to the slot content provider component (in our example: <Controls />
).
More precisely, the main purpose of <slot>Default HTML</slot>
(inside <Parent />
) is to either render Default HTML
when used as
<Parent />
, in which case Default HTML
's parent is <Parent />
, or to render Replacement HTML
when used as
<Parent>Replacement HTML</Parent>
, in which case Replacement HTML
's parent is the slot content provider (<Controls />
).
We can't tell Parent.vue
: "you know what, even if you're not in charge of providing the slot contents anymore, I still want you to meddle with it".
I mean, we can try, but slots were not developed for this purpose and there's no public API to hook into the rendering process of slots. Even if we figure out a way to do it today, we have no guarantee it won't break in the future, without warning.
If we find ourself needing anything from <Parent />
while crafting the slot contents (in <Controls />
) we can pass it from Parent to Controls using slot scope:
<!-- Parent.vue -->
<template>
<nav>
<slot :foo="foo" />
</nav>
</template>
<!-- Controls.vue -->
<Parent>
<template #default="{ foo }">
// use Parent's `foo` inside Controls
</template>
</Parent>
In our specific case, let's declare idx
prop on <Child />
and populate it from <Controls />
<!-- Controls.vue -->
<template>
<Parent>
<Child v-for="n in 3" :key="n" :idx="n - 1" />
</Parent>
</template>