svelte特殊元素

Svelte 提供了各种内置的特殊元素。

1、<svelte:self>

首个介绍的是<svelte:self>,它表示当前组件,允许在某些情况递归自身,这对于展示文件夹树之类的视图很有用,其中文件夹可以包含 其他 文件夹。

下方以展示文件夹树 Folder 为例:

Folder.svelte

<script>
  export let name = 'noname';
  export let folders = []
</script>

<style>
  ul { padding: 0.2em 0 0 1.5em; margin: 0 0 0 0.5em; list-style: none; }
  li { padding: 0.2em 0; }
</style>

<span>- {name}</span>

<ul>
  {#each folders as f}
    <li>
      <!-- 此处展示下一级的文件夹 -->
    </li>
  {/each}
</ul>

App.svelte

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

  let folders = [
    {	
      name: '音乐',
      folders: [
        { name: '流行音乐' },
        { name: '纯音乐' },
        { name: '吉他' }
      ]
    },
    {
      name: '电影',
      folders: [
        {
          name: '喜剧片', 
          folders: [
            { name: '周星驰' },
            { name: '黄渤' },
            { name: '沈腾' }
          ]
        },
        {
          name: '动作片',
          folders: [ { name: '李连杰' } ]
        }
      ]
    }
  ];
</script>

<Folder name="D盘" {folders} />

当前的结果只显示了根目录:

要显示所有子目录,这样是不可以的:

Folder.svelte

{#each folders as f}
  <li>
    <Folder {...f} />
  </li>
{/each}

也就是说,你不可以在 Folder.svelte 中直接使用 <Folder> 自身,因为模块不能导入自身。但是我们可以使用<svelte:self>代表自身:

Folder.svelte

{#each folders as f}
  <li>
    <svelte:self {...f} />
  </li>
{/each}

最终的结果:

基于 <svelte:self> 的作用是引用自身组件的构造函数,因此它十分容易导致堆栈溢出。

试想一下,Folder 组件中又使用了 Folder 组件,如果不加以“限制”,默认将会无穷无尽地渲染下去,直到爆栈报错。

所以,<svelte:self> 必须包裹在 #if 块或者 #each 块中,或者由插槽传给组件,其余情况均是禁止的。

2、<svelte:component>

组件可以通过<svelte:component>进行 “变身”,这样可以省掉一系列的if块...

例如我们有3个十分简单的组件(显示不同颜色的文本):

Red.svelte

<strong style="color: red">red text</strong>

Green.svelte

<strong style="color: green">green text</strong>

Blue.svelte

<strong style="color: blue">blue text</strong>

如果我们的主程序中需要根据某个下拉框的选择的不同,展示不同的颜色文本:

那么程序可能需要很多的 if 来做判断:

App.svelte

<script>
  import Red from './Red.svelte';
  import Green from './Green.svelte';
  import Blue from './Blue.svelte';

  let selected;
</script>

<select bind:value={selected}>
  <option value="red">红</option>
  <option value="green">绿</option>
  <option value="blue">蓝</option>
</select>

{#if selected == 'red'}
  <Red />
{:else if selected == 'green'}
  <Green />
{:else }
  <Blue />
{/if}

通过使用 <svelte:component>,可以消除掉诸多 if,更为动态地使用这3个组件:

App.svelte

<script>
  import Red from './Red.svelte';
  import Green from './Green.svelte';
  import Blue from './Blue.svelte';

  let selected = '0';
  $: component = [Red, Green, Blue][selected]
</script>

<select bind:value={selected}>
  <option value="0">红</option>
  <option value="1">绿</option>
  <option value="2">蓝</option>
</select>

<svelte:component this={component} />

this的值可以是任何组件的构造函数,如果提供的是一个假值,则不会渲染这个组件。

3、<svelte:window>

正如任何 DOM 元素都可以将监听事件一样,你可以通过<svelte:window>来监听window对象的事件,例如鼠标移动的事件:

App.svelte

<script>
  let x, y
</script>

<svelte:window on:mousemove={ e => ({x, y} = e) } />

<p>鼠标位置:{x}, {y}</p>
你也可以添加像  preventDefault 这样的  事件修饰符,这与 DOM 元素的方法并无二致。

绑定

我们还可以绑定一些window的属性,某些业务我们可能需要监测浏览器窗口的大小,原生的写法是通过监听 window.onresize 来实现的。

在 Svelte 中,它更为简单,并且支持绑定:

App.svelte

<script>
  let width, height
</script>

<svelte:window bind:innerWidth={width} bind:innerHeight={height} />

<p>Window Size: width={width}, height={height}</p>

支持绑定的属性列表如下:

  • innerWidth - 获得浏览器窗口的内容区域的宽度,包含垂直滚动条(如果有的话)
  • innerHeight - 获得浏览器窗口的内容区域的高度,包含水平滚动条(如果有的话)
  • outerWidth - 获得浏览器窗口的宽度
  • outerHeight - 获得浏览器窗口的高度
  • scrollX - document 在水平方向已滚动的像素值
  • scrollY - document 在垂直方向已滚动的像素值
  • online - window.navigator.onLine 的别名,代表当浏览器能够访问网络。

除了 scrollX 和 scrollY 两个属性之外,其余均是只读的。

4、<svelte:body>

<svelte:window>类似,<svelte:body>元素允许你监听document.body上的事件。这对于mouseentermouseleave事件来说十分有用,它们无法在window上触发。

App.svelte

<script>
  let msg = ''
</script>

<svelte:body
  on:mouseenter={() => msg = 'Enter'}
  on:mouseleave={() => msg = 'Leave'}
/>

<div>{msg}</div>

5、<svelte:head>

<svelte:head>元素允许你将其他元素插入到文档中的<head>中:

App.svelte

<svelte:head>
  <!-- 插入到 <head> 的内容写在这里 -->
</svelte:head>

<h1>Hello world!</h1>

<head> 中可以插入例如 <script><link><title> 和 <meta> 等等元素:

App.svelte

<svelte:head>
  <link rel="stylesheet" href="./global.css">
</svelte:head>
在服务器端渲染 (SSR) 模式下, <svelte:head> 的内容与 HTML 的其余部分是分别返回的。

6、<svelte:fragment>

回顾第13章《插槽》中的第3小节 —— 命名插槽,我们向命名插槽提供内容时,无可避免需要借助一个元素或者组件,例如:

<span slot="address">42 Wallaby Way Sydney</span>

如果没有 span 元素,那么 slot="address" 无处安放。

假设在某些情况下,我们借助别的元素还可能会影响到组件原有的布局,甚至要被迫放弃原有的 UI 描述方案,例如下方用于罗列菠萝包制作所需的材料列表的例子:

Material.svelte

<h4>菠萝包的食材用料:</h4>
<ul>
  <li>盐</li>
  <li>砂糖</li>
  <li>奶粉</li>
	
  <slot name="bottom" />
</ul>

<style>
  h4 { background: green; padding: 1em; color: #fff; }
</style>

例子中预留了一个位置供使用端补充更多的用料,比如在 bottom 位置再添加一个“高筋面粉”:

App.svelte

<script>
  import Material from './Material.svelte'
</script>

<Material>
  <li slot="bottom">高筋面粉</li>
</Material>

从展示的结果图来看,目前十分完美:

&amp;amp;amp;amp;lt;svelte:fragment&amp;amp;amp;amp;gt; 示例

菠萝包的制作用料中还有“低筋面粉”,如果你也想放到 bottom 位置,那么尴尬的事情就发生了:

App.svelte

<script>
  import Material from './Material.svelte'
</script>

<Material>
  <li slot="bottom">高筋面粉</li>
  <li slot="bottom">低筋面粉</li>
</Material>

Svelte 不允许提供两个相同 slot 名称的内容(参考第13章《插槽》中的第3小节),你将收到一个错误信息:“Duplicate slot name "bottom" in <Material>”。

下一个立即被想到的方案是这样:

App.svelte

<script>
  import Material from './Material.svelte'
</script>

<Material>
  <div slot="bottom">
    <li>高筋面粉</li>
    <li>低筋面粉</li>
  </div>
</Material>

为赋新词强说愁。强加了一个 div 作为容器,看看结果似乎又是对的:

但不要被表面蒙蔽,以下是实际的 HTML:

如果将所有用料使用横向布局,这个问题原形毕露:

Material.svelte

...
<style>
  h4 { background: green; padding: 1em; color: #fff; }

  /* 使列表呈横向布局 */
  ul { display: flex; }
  :global(.list li) { margin: auto 20px; }
</style>
你可能留意到,最后一个样式规则,使用的是  :global( ),这是因为通过  <slot> 添加的  <li> 用料项目, 不会自动应用与 Material 组件中的  <li> 相同的样式。
因为样式在组件中默认就是 局部作用域的。

这种情况下,你必须将这个样式使用 全局样式(通过 :global 伪类), <slot> 内容里的  <li> 才可以应用得 Material 中  <li> 的样式。

结果如下所示:

这就是前面所述的“可能会影响到组件原有的布局”的问题,<svelte:fragment> 正是为了解决这个问题而生,只需要将 <div> 改为 <svelte:fragment>,问题迎刃而解:

App.svelte

<script>
  import Material from './Material.svelte'
</script>

<Material>
  <svelte:fragment slot="bottom">
    <li>高筋面粉</li>
    <li>低筋面粉</li>
  </svelte:fragment>
</Material>

7、<svelte:options>

<svelte:options>允许你配置编译器的选项。

例如你希望为组件生成属性访问器(默认不生成):

App.svelte

<script>
  export let x = 'hello'
</script>

<svelte:options accessors />

<p>{x}</p>

通过给 svelte:options 加入 accessors 后,最终生成的 App 类如下:

class App extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, instance, create_fragment, safe_not_equal, { x: 0 });
  }

  get x() {
    return this.$$.ctx[0];
  }

  set x(x) {
    this.$set({ x });
    flush();
  }
}

export default App;

你可以留意到,变量 x 已经生成为 App 类的一个属性,如果没有指定这个编译选项,生成的 App 类则是这样:

class App extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, instance, create_fragment, safe_not_equal, { x: 0 });
  }
}

export default App;

生成属性访问器的目的,是提供给使用端更容易去访问的,这通常是你编写了一个 Svelte 组件,想同时在 React 或者 Vue 等地方上用时才需要这个选项。

下列是允许在此处设置可选用的值:

  • immutable={true}:你确信不会使用可变数据,因此编译器可以执行简单的引用相等性检查来确定值是否已更改。
  • immutable={false}:默认值,Svelte 将按默认的方式处理可变对象的更改
  • accessors={true}:为组件的 props 添加 getter 和 setter
  • accessors={false}:默认值
  • namespace="...":将使用此组件的命名空间,最常见的就是 "svg"
  • tag="...":将组件编译为自定义元素时,指定的标签名称

有关这些选项的详细信息,请参考API文档。

总结

对于 <svelte:options> 来说,99.9% 的情况下,你无需手工干预编译器的默认选项,除非你有十分明确的理由。

<svelte:fragment> 跟 Vue 的 <template> 功能近似,都是期望作为元素的容器,但同时自身无需渲染。

<svelte:window> 提供了一些十分有用的绑定,例如与尺寸相关的 innerWidth和 innerHeight,以及与滚动条相关的 scrollX 和 scrollY,这两个还支持赋值,它可以使编写这方面的程序变得更简单。

猜你喜欢

转载自blog.csdn.net/ramblerviper/article/details/124943638
今日推荐