简体   繁体   English

将 class 添加到插槽的每个孩子

[英]Add a class to every child of a slot

I'm trying to set up a component with a slot, that when rendered adds a class to every children of that slot.我正在尝试设置一个带有插槽的组件,该组件在渲染时会为该插槽的每个子级添加一个 class 。 In a very simplified manner:以非常简化的方式:

<template>
<div>
  <slot name="footerItems"></slot>
</div>
</template>

How would I go about this?我将如何 go 关于这个? My current solution is to add the class to the elements in an onBeforeUpdate hook:我当前的解决方案是将 class 添加到onBeforeUpdate挂钩中的元素中:

<script setup lang="ts">
import { useSlots, onMounted, onBeforeUpdate } from 'vue';

onBeforeUpdate(() => addClassToFooterItems());
onMounted(() => addClassToFooterItems());

function addClassToFooterItems() {
  const slots = useSlots();

  if (slots && slots.footerItems) {
    for (const item of slots.footerItems()) {
      item.el?.classList.add("card-footer-item");
    }
  }
}
</script>

However, the elements lose the styling whenever it's rerendered (using npm run serve ) and also jest tests give me a warning:但是,元素在重新渲染时会丢失样式(使用npm run serve )并且开玩笑测试给了我一个警告:

    [Vue warn]: Slot "footerItems" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.

Should I move the slot to its own component and use a render function there?我应该将插槽移动到它自己的组件并在那里使用渲染 function 吗? But even then I'm not sure how to edit the children to add the classes or how to produce several root level elements from the render function.但即便如此,我也不确定如何编辑子级以添加类或如何从渲染 function 生成几个根级元素。

You need the class for apply some style?您需要 class 来应用某种风格吗?

You can pass the style or class with props您可以通过带有道具的样式或 class

And apply the class or style with :style="" or :class="" , it's explained here并应用 class 或带有:style="":class=""的样式,这里解释

But just for apply a class, just :class="" solves your problem但只是为了申请 class,只需:class=""即可解决您的问题

So, I managed to solve this in an incredibly hacky way, but at least my issue with re-rendering doesn't happen anymore and jest doesn't complain.所以,我设法以一种令人难以置信的 hacky 方式解决了这个问题,但至少我的重新渲染问题不再发生,并且 jest 也没有抱怨。 I wrote a component with a render function that appends that class to all children我编写了一个带有渲染 function 的组件,它将 class 附加到所有孩子


<template>
<render>
  <slot></slot>
</render>
</template>

<script setup lang="ts">
import { useSlots } from 'vue';

const props = defineProps<{
  childrenClass: string;
}>();

function recurseIntoFragments(element: any): any {
  if (element.type.toString() === 'Symbol(Fragment)'
    && element.children[0].type.toString() === 'Symbol(Fragment)'
  ) {
    return recurseIntoFragments(element.children[0]);
  } else {
    return element;
  }
}

const render = () => {

  const slot = useSlots().default!();
  recurseIntoFragments(slot[0]).children.forEach((element: any) => {
    if (element.props?.class && !element.props?.class.includes(props.childrenClass)) {
      element.props.class += ` ${props.childrenClass}`;
    } else {
      element.props.class = props.childrenClass;
    }
  });

  return slot;
}
</script>

Then I would just wrap the slot in this component to add the class to the children elements:然后我只需将插槽包装在此组件中,以将 class 添加到子元素中:

<template>
<div>
  <classed-slot childrenClass="card-footer-item">
    <slot name="footerItems"></slot>
  </classed-slot>
</div>
</template>

I would gladly accept any answer that improves upon this solution, especially:我很乐意接受任何改进此解决方案的答案,尤其是:

  • Any tips to type it.键入它的任何提示。 All those any s feel wonky but I find it very impractical working with Vue types for slots since they are usually unions of 3 or 4 types and the only solution is to wrap these in type checks所有这些any感觉很奇怪,但我发现使用 Vue 类型的插槽非常不切实际,因为它们通常是 3 或 4 种类型的联合,唯一的解决方案是将它们包装在类型检查中
  • Anything that improves its reliability since it seems that it'd crash in any slightly different setup than the one I intended任何可以提高其可靠性的东西,因为它似乎会在与我预期的设置略有不同的设置中崩溃
  • Any recommendation based on Vue's (or TS) best practices, since this looks very amateurish.任何基于 Vue(或 TS)最佳实践的建议,因为这看起来很业余。
  • Really any other way to test for symbol equality because I know none真的有任何其他测试符号相等的方法,因为我不知道

EDIT This is my latest attempt, a render function in a file ClassedSlot.js :编辑这是我最新的尝试,在 ClassedSlot.js 文件中渲染ClassedSlot.js

import { cloneVNode } from 'vue';

function recursivelyAddClass(element, classToAdd) {
  if (Array.isArray(element)) {
    return element.map(el => recursivelyAddClass(el, classToAdd));
  } else if (element.type.toString() === 'Symbol(Fragment)') {
    const clone = cloneVNode(element);
    clone.children = recursivelyAddClass(element.children, classToAdd)
    return clone;
  } else {
    return cloneVNode(element, { class: classToAdd });
  }
}

export default {
  props: {
    childrenClass: {
      type: String,
      required: true
    },
  },

  render() {
    const slot = this.$slots.default();

    return recursivelyAddClass(slot, this.$props.childrenClass);
  },
};

The usage of this component is exactly the same as in the previous one.该组件的用法与上一个完全相同。 I'm kinda happy with this solution, seems more robust and idiomatic.我对这个解决方案有点满意,看起来更健壮和惯用。 Note that it's javascript because I found it really hard to type these functions correctly.请注意,它是 javascript,因为我发现很难正确键入这些函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM