svelte模板

除去脚本,组件的另一个核心点是 UI,用于描述 UI 的 HTML 是一种“静态”的语言,HTML 是无法表达 逻辑 的,比如条件和循环,但 Svelte 可以。Svelte 为其增加了一些逻辑支持,譬如判断、遍历等,以增强你对 UI 的表达能力。

这些额外附加的逻辑能力似曾相识,它十分接近一些以往非常流行的 Handlebars 或者 Mustache 模板语言,例如 { {#if ...}},稍有区别的是,Svelte 使用单个大括号括起:{#if ...}

Svelte 的编译器将会编译这些逻辑,通过将 HTML 编译成使用原生 JS 创建的形式,也即这些 HTML 最终将使用 createElement 等原生的 DOM API 来创建。

解释 HTML 模板时,Svelte 就已经明确哪些状态需要更新。

1、If 块

先来参看这些代码:

App.svelte

<script>
  let user = { loggedIn: false };

  function toggle() {
    user.loggedIn = !user.loggedIn;
  }
</script>

<button on:click={toggle}>Log out</button>
<button on:click={toggle}>Log in</button>

为了可以按条件渲染某些标记,我们将其放置在 if 块中:

App.svelte

<script>
  let user = { loggedIn: false };

  function toggle() {
    user.loggedIn = !user.loggedIn;
  }
</script>

{#if user.loggedIn}
  <button on:click={toggle}>Log out</button>
{/if}

{#if !user.loggedIn}
  <button on:click={toggle}>Log in</button>
{/if}

当然,这仅仅是为了演示,实际上我们知道两个 button 唯一的区别是“out”和“in”两个单词之差,完全可以使用一个 ? 号表达式来替代上述繁琐的 if。

2、Else 块

由于if user.loggedInif !user.loggedIn这两个条件是互斥的,因此可以使用else块来简化它:

App.svelte

扫描二维码关注公众号,回复: 15998417 查看本文章
<script>
  let user = { loggedIn: false };

  function toggle() {
    user.loggedIn = !user.loggedIn;
  }
</script>

{#if user.loggedIn}
  <button on:click={toggle}>Log out</button>
{:else}
  <button on:click={toggle}>Log in</button>
{/if}
# 字符代表  块开始 标记。 / 字符代表  块结束 标记。而  {:else} 中的  : 字符,则表示  块连续 标记。不用担心,至此你几乎已经学完 Svelte 为 HTML 添加的所有语法了。

3、Else-If 块

假设我们已有如下代码:

App.svelte

<script>
  let x = 7;
</script>

{#if x > 10}
  <p>{x} 大于 10</p>
{:else}
  {#if 5 > x}
    <p>{x} 小于 5</p>
  {:else}
    <p>{x} 在 5 和 10 之间</p>
  {/if}
{/if} 

可以使用else if将多个条件 “连接” 在一起:

App.svelte

<script>
  let x = 7;
</script>

{#if x > 10}
  <p>{x} 大于 10</p>
{:else if 5 > x}
  <p>{x} 小于 5</p>
{:else}
  <p>{x} 在 5 和 10 之间</p>
{/if} 

4、each 块

如果你需要遍历数据列表,请使用each块,如下方代码,我们有一个数组需要遍历:

App.svelte

<script>
  let cats = [
    { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
    { id: 'z_AbfPXTKms', name: 'Maru' },
    { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
  ];
</script>

<h1>The Famous Cats of YouTube</h1>

<ul>
  {#each cats as cat}
    <li>
      <a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
        {cat.name}
      </a>
    </li>
  {/each}
</ul>
这个例子中的表达式  cats 的地方,事实上可以是任意数组或类数组的对象(只要它具有  length 属性)。你可以使用  each [...iterable] 来遍历迭代器。

你可以将当前的 index 索引作为第二个参数,如下所示:

App.svelte

<script>
  let cats = [
    { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
    { id: 'z_AbfPXTKms', name: 'Maru' },
    { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
  ];
</script>

<h1>The Famous Cats of YouTube</h1>

<ul>
  {#each cats as cat, i}
    <li>
      <a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
        {i + 1}:{cat.name}
      </a>
    </li>
  {/each}
</ul>

只要你愿意,还可以使用解构语法each cats as { id, name }, i,也即是用idname来代替cat.idcat.name

5、提供 key 的 each 块

默认情况下,当你修改 each 块的值时,它将该块的 结束 位置添加和删除项,并更新所有已更改的值。不过那可能不是你想要的。

示例胜千言,不过这个示例还挺复杂的,分为两个组件:

Thing.svelte

<script>
  // 每当 prop 的值更改时,`current` 都会更新...
  export let current;

  // ...但 `initial` 初始化后就固定不变。
  const initial = current;
</script>

<p>
  <span style="background-color: {initial}">initial</span>
  <span style="background-color: {current}">current</span>
</p>

<style>
  span {
    display: inline-block;
    padding: 0.2em 0.5em;
    margin: 0 0.2em 0.2em 0;
    width: 4em;
    text-align: center;
    border-radius: 0.2em;
    color: white;
  }
</style>

App.svlete

<script>
  import Thing from './Thing.svelte';

  let things = [
    { id: 1, color: 'darkblue' },
    { id: 2, color: 'indigo' },
    { id: 3, color: 'deeppink' },
    { id: 4, color: 'salmon' },
    { id: 5, color: 'gold' }
  ];

  function handleClick() {
    things = things.slice(1);
  }
</script>

<button on:click={handleClick}>Remove first thing</button>

{#each things as thing}
  <Thing current={thing.color}/>
{/each}

点击几次 “Remove first thing” 按钮,需要注意的是,它从最后位置删除 <Thing>,并为剩余的组件更新了 color

假设我们期望删除第一个 <Thing> 组件,其余的不受影响。

为此,我们为 each 块指定一个唯一的 ID:

{#each things as thing (thing.id)}
  <Thing current={thing.color}/>
{/each}

这个 (thing.id) 告知 Svelte 如何计算变化的地方。

你可以使用任何对象作为 key,因为 Svelte 内部使用的是  Map 来实现的,换而言之,你甚至可以使用  (thing) 代替  (thing.id)。但是字符串或数字通常会更安全,因此这代表 ID 保持不变,且没有引用相等性问题,例如使用来自 API 服务传回来的新数据进行更新的时候。

6、await 块

大多数Web应用程序必须在某个时候处理异步数据,比如下方的代码:

App.svelte

<script>
  async function getRandomNumber() {
    const res = await fetch(`tutorial/random-number`);
    const text = await res.text();

    if (res.ok) {
      return text;
    } else {
      throw new Error(text);
    }
  }

  let promise = getRandomNumber();

  function handleClick() {
    promise = getRandomNumber();
  }
</script>

<button on:click={handleClick}>生成随机数字</button>

<!-- 我们稍候将在此处使用 await 块 -->
<p>{promise}</p>

Svelte使您可以轻松地在标记中直接 await Promise的值:

App.svelte

<script>
  async function getRandomNumber() {
    const res = await fetch(`https://svelte.dev/tutorial/random-number`);
    const text = await res.text();

    if (res.ok) {
      return text;
    } else {
      throw new Error(text);
    }
  }

  let promise = getRandomNumber();

  function handleClick() {
    promise = getRandomNumber();
  }
</script>

<button on:click={handleClick}>生成随机数字</button>

{#await promise}
  <p>{promise}</p>
{:then number}
  <!-- 返回的结果使用 :then 块连续标记,
       后面提供接收Promise resolve 的变量名称
  -->
  <p>The number is {number}</p>
{:catch error}
  <!-- 如果发生错误,在 :catch 子块中接收错误对象 -->
  <p style="color: red">{error.message}</p>
{/await}
仅考虑最新的  promise,这说明你无需顾虑 race 情况。

如果你事前就明确了 promise 不会 reject,则可省略 catch 块。如果在 promise 被 resolve 之前不想显示任何内容,甚至也可以省略第一个块:

App.svelte

<script>
  async function getRandomNumber() {
    const res = await fetch(`https://svelte.dev/tutorial/random-number`);
    const text = await res.text();

    if (res.ok) {
      return text;
    } else {
      throw new Error(text);
    }
  }

  let promise = getRandomNumber();

  function handleClick() {
    promise = getRandomNumber();
  }
</script>

<button on:click={handleClick}>生成随机数字</button>

{#await promise then number}
  <p>The number is {number}</p>
{/await}

7、key 块

现在我们有一个需求是展示一个数字,点击按钮每次加 2:

App.svelte

<script>
  let num = 0
</script>

<p>{num}</p>
<button on:click={ () => num += 2 }> +2 </button>

如果我们希望数字变化时使用“淡入”的效果,在不使用 Svelte 内置的 transition 的情况下(动画相关的内容在后续章节有详尽的讲述),可能需要一个定时器配合 opacity 样式规则来做:

App.svelte

<script>
  let num = 0
  let opacity = 1
  let duration = 300
	
  function plus2() {
    opacity = 0
		
    setTimeout(() => {
      opacity = 1
      num += 2
    }, duration)
  }
</script>

<p style="transition: {duration}ms; opacity: {opacity};">{num}</p>
<button on:click={ plus2 }> +2 </button>

这显得繁琐了些,我们使用内置的 fade

App.svelte

<script>
  import { fade } from 'svelte/transition'
  let num = 0
</script>

<p in:fade>{num}</p>
<button on:click={ () => num += 2 }> +2 </button>

因为自带的 fade 淡入效果需要元素创建时才会展现,如你所见上述代码点击 +2 按钮并没有任何“动静”。

如何能够让 Svelte 知道:num 这个变量的值被修改(每次 +2,状态变化) 后,p 元素需要强制销毁,并重新渲染?

这就是 key 块的用途,事实上它的作用与第5节《提供 key 的 each 块》的作用是一样的,所不同的是一个应用在可迭代的对象,而 key 可以应用在任意状态。

现在我们通过加入 key 块轻松解决这个问题,现在它满足需求了:

App.svelte

<script>
  import { fade } from 'svelte/transition'
  let num = 0
</script>

{#key num}
  <p in:fade>{num}</p>
{/key}
<button on:click={ () => num += 2 }> +2 </button>

只要 num 的值改变了,key 块内的元素都会被销毁重新渲染

总结

本章已涵盖了模板逻辑的全部内容,长篇累牍。

用得较多的通常会是 #each 块,包括使用了 key 的 each 块,使用 key 目的是计算出哪些是没有变化的元素,这样可以不需要重新创建它,以便减少 DOM 渲染的工作量。

猜你喜欢

转载自blog.csdn.net/ramblerviper/article/details/124926854