如何根据 Svelte 中的道具设置动态 html 标签

How to set dynamic html tag according to props in Svelte

我正在 svelte 中创建一个 Heading 组件,作为学习该框架基础知识的一部分。组件行为非常简单。

该组件将有一个名为 level 的道具,它将相应地呈现适当的 <h> 标签。

例如。

<Heading level={3}> would render <h3>content</h3> 
<Heading level={1}> would render <h1>content</h1>

我目前可以做到这一点,

<script>
  export let level = 3;
</script>

{#if level === 1}
  <h1>
   <slot></slot>
  </h1>
{:else if level === 2}
  <h2>
   <slot></slot>
  </h2>
{:else if level === 3}
  <h3>
   <slot></slot>
  </h3>
{:else if level === 4}
  <h4>
   <slot></slot>
  </h4>
{:else if level === 5}
  <h5>
   <slot></slot>
  </h5>
{/if}

但这种感觉是一种非常幼稚的做法。 在 svelte ?

中有没有更好的方法来实现这种行为

试试这个:

<script>
export let level = 3;
let displayText = "<h" + level + ">" +
                    "Sample header text" +
                  "</h" + level + ">";
</script>

<main>
    <div>
        {@html displayText}
    </div>
</main>

您可以通过将 level 的值连接到标记中来构建您的 html 字符串,然后使用“@html”变量注释显示它,它将您的字符串解释为 html,而不是纯文本。

我知道你的问题是为了向组件提供一个数字道具来设置航向级别,但是因为你用这个问题结束了你的 OP:

But this kind of feels like a very naive approach. Is there any better way to achieve this behaviour in svelte ?

...为了让未来的读者受益,这里有一个更强大的解决方案来解决需要一个组件来生成标题标签的核心问题。

如果您不介意使用页面上每个内容部分的包装器组件。您需要:

  • 用于设置上下文的包装器组件
  • 一个生成标题标签的组件
    • 标题标签组件接受一个 prop,因此您可以向其提供所需的内容

这是一个例子:

Section.svelte

<script>
  import { setContext, getContext } from 'svelte'

  let level

  // if we find a context has already been set in this component tree, 
  // it came from a parent/ancestor instance of Section.svelte

  if (getContext('headingLevel')) {
    // Increment the context because this is the next nesting level
    level = getContext('headingLevel') + 1
    setContext('headingLevel', level)
  } else {
    // otherwise this instance is the first of its kind in the hierarchy
    level = 2
    setContext('headingLevel', level)
  }
</script>

<section>
  <slot />
</section>

HeadingTag.svelte

<script>
  import { getContext } from 'svelte'

  // prop to insert your desired contents into the heading tag
  export let message

  // get the context, but make sure we can't go higher than <h6>
  let level = Math.min(getContext('headingLevel'), 6)

  const render = () => `
    <h${level}>
      ${message}
    </h${level}>
  `

</script>

{@html render()}

然后,在你的其他组件或页面中,这样使用就可以了

MyPage.svelte

<Section>
  <HeadingTag message={"hello"} />
  <!-- renders <h2>hello</h2> -->
  <Section>
    <HeadingTag message={"hello"} />
    <!-- renders <h3>hello</h3> -->
    <Section>
      <HeadingTag message={"hello"} />
      <!-- renders <h4>hello</h4> -->
    </Section>
  </Section>
</Section>

<Section>
  <HeadingTag message={"hello"} />
  <!-- renders <h2>hello</h2> -->
</Section>

无论您的组件是如何嵌套的,这都可以无缝工作。

请注意,我的示例将其设置为从 <h2> 开始,假设每个页面在其 <main> 中只有一个 <h1>,而事实并非如此需要这种自动化。但是您可以根据需要使其适应您的用例,例如如果您希望它以 top-level 处的 <h1> 开头...

Section.svelte

<script>
  import { setContext, getContext } from 'svelte'

  let level

  if (getContext('headingLevel')) {
    level = getContext('headingLevel') + 1
    setContext('headingLevel', level)
  } else {
    // this and the HTML below are the only things that changed
    level = 1
    setContext('headingLevel', level)
  }
</script>

{#if level === 1}
  <main>
    <slot />
  </main>
{:else}
  <section>
    <slot />
  <section>
{/if}

归功于到期的地方,感兴趣的人可以进一步阅读:我在 Heydon Pickering 的这篇文章中的 React-based 示例中采用了 Svelte 中的这个解决方案:

https://medium.com/@Heydon/managing-heading-levels-in-design-systems-18be9a746fa3

Svelte 从 3.47.0 开始使用 svelte:element 标签对此提供原生支持。示例:

<svelte:element this="h1">this will be rendered as a top-level heading</svelte:element>

<svelte:element this={tag}>will render the element named in 'tag'</svelte:element>

<svelte:element this={null}>will not render for falsey values</svelte:element>

有关详细信息,请参阅 the docs or the tutorial