Выполнение js в слоте

#vue.js #vue-component #vue-slot

Вопрос:

Я новичок в веб-разработке и пытаюсь помочь друзьям перезапустить старую игру. Я отвечаю за компонент подсказок, но я наткнулся на стену…

Существует много компонентов Vue, и во многих из них я хочу вызвать дочерний компонент с именем Tooltip, который я использую vue-tippy для удобства настройки. Это компонент:

 <template>
    <tippy class="tippy-tooltip">
      <slot name='tooltip-trigger'></slot>

      <template #content>
          <slot name='tooltip-content'>
          </slot>
      </template>
    </tippy>
</template>

<script>
import { formatText } from "@/utils/formatText";

    export default {
    name: "Tooltip",
    methods:{
        formatContent(value) {
            if (! value) return '';
            return formatText(value.toString());
            }
        },
    }
</script>
 

В одном из других компонентов я пытаюсь использовать подсказку:

 <template>
    <a class="action-button" href="#">
        <Tooltip>
            <template #tooltip-trigger>
                <span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="@/assets/images/pm.png" alt="mp"></span>
                <span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="@/assets/images/pa.png" alt="ap"></span>
                <span v-if="action.canExecute">{{ action.name }}</span>
                <span v-else><s>{{ action.name }}</s></span>
                <span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
            </template>
            <template #tooltip-content>
                <h1>{{action.name}}</h1>
                <p>{{action.description}}</p>
            </template>
        </Tooltip>
    </a>
</template>

<script>
import Tooltip from "@/components/Utils/ToolTip";

export default {
    props: {
        action: Object
    },
    components: {Tooltip}
};
</script>
 

Отсюда все в порядке, всплывающая подсказка правильно отображается с соответствующим содержимым.

Дело в том, что текст в {{ named.description }} нем должен быть отформатирован вместе с formatContent содержимым. Я знаю, что могу использовать реквизит, компоненты будут выглядеть так:

Tooltip.vue:

 <template>
    <tippy class="tippy-tooltip">
      <slot name='tooltip-trigger'></slot>

      <template #content>
          <h1 v-html="formatContent(title)" />
          <p v-html="formatContent(content)"/>
      </template>
    </tippy>
</template>

<script>
import { formatText } from "@/utils/formatText";

    export default {
    name: "Tooltip",
    methods:{
        formatContent(value) {
            if (! value) return '';
            return formatText(value.toString());
            }
        },
    props: {
        title: { 
            type: String,
            required: true
            },
        content: { 
            type: Array,
            required: true
            }
        }
    }
</script>
 

Родитель.vue:

 
<template>
    <a class="action-button" href="#">
        <Tooltip :title="action.name" :content="action.description">
            <template v-slot:tooltip-trigger>
                <span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="@/assets/images/pm.png" alt="mp"></span>
                <span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="@/assets/images/pa.png" alt="ap"></span>
                <span v-if="action.canExecute">{{ action.name }}</span>
                <span v-else><s>{{ action.name }}</s></span>
                <span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
            </template>
        </Tooltip>
    </a>
</template>

<script>
import Tooltip from "@/components/Utils/ToolTip";

export default {
    props: {
        action: Object
    },
    components: {Tooltip}
};
</script>
 

Но мне нужно использовать слот в компоненте всплывающей подсказки, потому что у нас будет несколько «обширных» списков v-for .

Есть ли способ передать данные из слота в функцию JS?

Ответ №1:

Если я вас правильно понял, вы ищете здесь слоты с областью действия.

Это позволит вам передавать информацию (включая методы) от дочерних компонентов (компонентов с <slot> элементами) обратно родителям (компонентам, заполняющим эти ячейки), позволяя родителям использовать выбранную информацию непосредственно в встроенном содержимом.

В этом случае мы можем предоставить родителям доступ formatContent() , который позволит им передавать контент , который использует его напрямую. Это позволяет нам сохранять гибкость слотов при передаче данных реквизитов.

Чтобы добавить это к вашему примеру, мы добавим некоторую «область действия» в ваш слот для контента Tooltip.vue . Это просто означает, что мы один или несколько атрибутов вашего <slot> элемента, в данном случае, formatContent :

 <!-- Tooltip.vue -->
<template>
    <tippy class="tippy-tooltip">
      <slot name='tooltip-trigger'></slot>

      <template #content>
          <!-- Attributes we add or bind to this slot (eg. formatContent) -->
          <!-- become available to components using the slot -->
          <slot name='tooltip-content' :formatContent="formatContent"></slot>
      </template>
    </tippy>
</template>

<script>
import { formatText } from "@/utils/formatText";

export default {
    name: "Tooltip",
    methods: {
        formatContent(value) {
            // Rewrote as a ternary, but keep what you're comfortable with
            return !value ? '' : formatText(value.toString());
        }
    },
}
</script>
 

Теперь, когда мы добавили некоторую область действия в слот, родители, заполняющие слот контентом, могут использовать его, вызвав «область действия»слота:

 <!-- Parent.vue -->
<template>
    <a class="action-button" href="#">
        <Tooltip>
            . . .
            <template #tooltip-content="{ formatContent }">
                <!-- Elements in this slot now have access to 'formatContent' -->
                <h1>{{ formatContent(action.name) }}</h1>
                <p>{{ formatContent(action.description) }}</p>
            </template>
        </Tooltip>
    </a>
</template>

. . . 
 

Примечание: Я предпочитаю использовать деструктурированный синтаксис для области действия слота, потому что я чувствую, что это понятнее, и вам нужно раскрывать только то, что вы на самом деле используете:

 <template #tooltip-content="{ formatContent }">
 

Но вы также можете использовать здесь имя переменной, если хотите, которое станет объектом, имеющим все содержимое вашего слота в качестве свойств. Напр..:

 <template #tooltip-content="slotProps">
    <!-- 'formatContent' is now a property of 'slotProps' -->
    <h1>{{ slotProps.formatContent(action.name) }}</h1>
    <p>{{ slotProps.formatContent(action.description) }}</p>
</template>
 

Если вам все еще нужен v-html рендеринг, вы все равно можете сделать это в слоте:

 <template #tooltip-content="{ formatContent }">
    <h1 v-html="formatContent(title)" />
    <p v-html="formatContent(content)"/>
</template>
 

Комментарии:

1. Это действует как заклинание ! У меня все еще есть небольшая проблема. formatContent() Замените html каким-нибудь символом. Например :pm: заменяется на an <img ....> . Поскольку v-html он больше не используется, отображается текст rawHTML. Есть ли способ обработать это ?

2. Добавлен еще один пример; вы все равно сможете использовать v-html его так же, как и раньше, вы просто включите его <h1> и <p> перейдете в слот.