首页 > 解决方案 > 细长的包裹每个插槽

问题描述

我希望在 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 文件中包装每个按钮。

标签: 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);
    }
  });
}

就像说的那样,这有效并且消除了为简单情况创建单独包装器的需要,但更复杂的包装器需要重新工作。如果有人有更简单、更高效的更好建议,我会全力以赴。


推荐阅读