svelte - 细长的包裹每个插槽
问题描述
我希望在 svelte 中创建一个 Spacer 元素。我已经完成了我所设定的,即允许用户将元素包装在<Space></Space>
标签中并传递道具以垂直/水平均匀地间隔它们,甚至将内部元素中心/开始/结束/基线对齐。我遇到的问题是我必须<Space></Space>
用 a包装每个子元素以<SpaceItem></SpaceItem>
获得所需的效果。这是示例代码:
App.svelte
<script>
import {Space, SpaceItem} from './components/Space';
</script>
<Space>
<SpaceItem>
<Button primary>Primary</Button>
</SpaceItem>
<SpaceItem>
<Button primary icon="add">Primary</Button>
</SpaceItem>
<SpaceItem>
<Button danger>Danger</Button>
</SpaceItem>
</Space>
空间.svelte
<script>
import { SPACE } from './SpaceContext.svelte';
import { setContext } from 'svelte';
import { readable } from 'svelte/store';
export let vertical = false;
export let horizontal = true;
export let alignCenter = false;
export let alignStart = false;
export let alignEnd = false;
export let alignBaseline = false;
export let large = false;
export let middle = false;
export let size = null;
horizontal = vertical ? false : true;
size = size ? size : large ? 24 : middle ? 16 : 8;
const gap = readable(size);
const direction = readable(horizontal ? 'horizontal' : 'vertical');
setContext(SPACE, {
gap,
direction,
});
</script>
<style>
.space {
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
}
.vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.align-center {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.align-start {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.align-end {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.align-baseline {
-webkit-box-align: baseline;
-ms-flex-align: baseline;
align-items: baseline;
}
</style>
<div
class="space"
class:horizontal
class:vertical
class:size
class:align-center={alignCenter}
class:align-start={alignStart}
class:align-end={alignEnd}
class:align-baseline={alignBaseline}>
<slot />
</div>
空间项目
<script>
import { SPACE } from './SpaceContext.svelte';
import { getContext } from 'svelte';
const { gap, direction } = getContext(SPACE);
let style;
if ($direction === 'horizontal') {
style = `margin-right: ${$gap}px;`;
} else {
style = `margin-bottom: ${$gap}px;`;
}
</script>
<style>
</style>
<div class="item" {style}>
<slot />
</div>
有没有办法消除<SpaceItem></SpaceItem>
元素并用 ?<Space></Space>
包装传递给它<slot />
的每个元素<SpaceItem></SpaceItem>
?我只想将所有元素包装在一个 Space 标记中,而不是将每个单独的元素包装在 SpaceItem 标记中。
想要的效果是让我能够写:
App.svelte
<script>
import {Space, SpaceItem} from './components/Space';
</script>
<Space>
<Button primary>Primary</Button>
<Button primary icon="add">Primary</Button>
<Button danger>Danger</Button>
</Space>
并且仍然<SpaceItem></SpaceItem>
在 Space.svelte 文件中包装每个按钮。
解决方案
这是我自己制定的一个解决方案,但我不确定这是否会在现实世界的应用程序中出现任何性能问题,因此欢迎发表评论。
App.svelte
<script>
import Space from './Space.svelte';
import Button from './Button.svelte';
</script>
<Space>
<Button primary>Primary</Button>
<Button primary icon="add">Primary</Button>
<Button danger>Danger</Button>
</Space>
<Space vertical large alignCenter>
<Button primary icon="add">Primary</Button>
<Button danger>Danger</Button>
</Space>
空间.svelte
<script>
import { onMount } from 'svelte';
import addWrapper from './utils/addWrapper';
export let vertical = false;
export let horizontal = true;
export let alignCenter = false;
export let alignStart = false;
export let alignEnd = false;
export let alignBaseline = false;
export let large = false;
export let middle = false;
export let size = null;
horizontal = vertical ? false : true;
size = size ? size : large ? 24 : middle ? 16 : 8;
const gap = size;
const direction = horizontal ? 'horizontal' : 'vertical';
let style;
if (direction === 'horizontal') {
style = `margin-right: ${gap}px;`;
} else {
style = `margin-bottom: ${gap}px;`;
}
let currentEl;
onMount(async () => {
addWrapper({
node: currentEl,
tag: 'div',
style,
});
});
</script>
<style>
.space {
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
}
.vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.align-center {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.align-start {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.align-end {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.align-baseline {
-webkit-box-align: baseline;
-ms-flex-align: baseline;
align-items: baseline;
}
</style>
<div
bind:this={currentEl}
class="space"
class:horizontal
class:vertical
class:align-center={alignCenter}
class:align-start={alignStart}
class:align-end={alignEnd}
class:align-baseline={alignBaseline}
>
<slot />
</div>
addWrapper.js
export default async function addWrapper({ node, tag, style }) {
const children = node.childNodes;
[].forEach.call(children, function (child) {
if (child.nodeName !== "#text" && child.nodeName !== "#comment") {
let wrapper = document.createElement(tag);
wrapper.setAttribute("style", style);
child.parentNode.insertBefore(wrapper, child);
wrapper.appendChild(child);
}
});
}
就像说的那样,这有效并且消除了为简单情况创建单独包装器的需要,但更复杂的包装器需要重新工作。如果有人有更简单、更高效的更好建议,我会全力以赴。