#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>
перейдете в слот.