svelte: 带有 DOM 元素的组件

Nic*_*ick 2 svelte svelte-component svelte-3

目标

我正在 Svelte 中创建一个按钮组件,它将呈现为<button><a>元素,具体取决于它是否是链接。可以使用svelte:component吗?

像这样的东西……

<script lang='ts'>
  export let href: string = ''

  $: component = href ? a : button // where "a" and "button" are the HTML DOM elements
</script>

<svelte:component this={component}>
 <slot></slot>
</svelte:component>
Run Code Online (Sandbox Code Playgroud)

到目前为止,我只看到了svelte:component渲染自定义 Svelte 组件的示例,而不是 DOM 元素

https://svelte.dev/tutorial/svelte-component

如何在 Svelte 中动态渲染组件?

动机

可以使用 if/else 来获得所需的结果......

<script lang='ts'>
  export let href: string = ''
</script>

{# if href}
  <a {href}>
    <slot></slot>
  </a>
{:else}
  <button>
    <slot></slot>
  </button>
{/if}
Run Code Online (Sandbox Code Playgroud)

……但这不可维护。

  • 整个内容被复制。在上面的简单示例中,内容只是孩子。实际上,有多个插槽(例如,前缀/后缀),导致大量重复的逻辑。
  • 我可以看到许多其它组件为这种设计模式的用途具有多于2点的变体(例如,Container其可以是一个组件divsectionarticleaside,等等)。更多的变体导致更混乱的代码结构。

反应等价物

这是具有所需功能的示例 React 组件。

const Button = (props) => {
  const Tag = props.href ? 'a' : 'button'

  return <Tag href={props.href}>{contents}</Tag>
}
Run Code Online (Sandbox Code Playgroud)

我希望避免的解决方案

1. if/else 模式

和上面一样的模式。

2. 创建一个“子”组件

您可以将其移动到自己的组件中,然后将子项导入 if/else 链,而不是多次复制子项。至少那时不会有重复的逻辑,但道具/插槽将需要重复。

<script lang='ts'>
  import Children from './children.svelte'

  export let href: string = ''
</script>

{# if href}
  <a {href}>
    <Children><slot></slot></Children>
  </a>
{:else}
  <button>
    <Children><slot></slot></Children>
  </button>
{/if}
Run Code Online (Sandbox Code Playgroud)

3. 创建包装组件

对于每个包装器 DOM 元素,只需创建一个新的 Svelte 组件。然后,将它们作为实际的 Svelte 组件导入并使用svelte:component

<!-- dom-a.svelte -->

<a {...$$props}><slot></slot></a>
Run Code Online (Sandbox Code Playgroud)
<!-- dom-button.svelte -->

<button {...$$props}><slot></slot></button>
Run Code Online (Sandbox Code Playgroud)
<!-- button.svelte -->
<script lang='ts'>
  import A from './dom-a.svelte'
  import Button from './dom-button.svelte'

  export let href: string = ''

  $: component = href ? A : Button
</script>

<svelte:component this={component}>
 <slot></slot>
</svelte:component>
Run Code Online (Sandbox Code Playgroud)

虽然这是作为开发人员使用的最佳方式,但使用未知道具会导致性能下降。因此,这不是想法。

我想你可以在dom-button.sveltedom-a.svelte组件中指定每一个可能的道具,但这似乎有点矫枉过正。

Geo*_*ich 5

不能<svelte:component>用于此目的。有人提议<svelte:element>为此功能添加新标签,但尚未实施。有一个公开的 PR可以将此标签添加到 Svelte。

实施后,您将能够执行以下操作:

<script lang='ts'>
  export let href: string = ''

  $: tagName = href ? 'a' : 'button';
</script>

<svelte:element tag={tagName}>
  <slot></slot>
</svelte:element>
Run Code Online (Sandbox Code Playgroud)

在此之前,解决方法如您在问题中所述。