Svelte 官方入门教程(14) - 特殊元素

svelte cover

上一讲是上下文的相关知识,本讲说下svelte中的特殊元素

一、<svelte:self>

Svelte 提供了多种内置元素。 第一个,<svelte:self>,允许组件递归地包含自身。

看个例子:

App.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script>
import Folder from './Folder.svelte';

let root = [
{
name: 'Important work stuff',
files: [
{ name: 'quarterly-results.xlsx' }
]
},
{
name: 'Animal GIFs',
files: [
{
name: 'Dogs',
files: [
{ name: 'treadmill.gif' },
{ name: 'rope-jumping.gif' }
]
},
{
name: 'Goats',
files: [
{ name: 'parkour.gif' },
{ name: 'rampage.gif' }
]
},
{ name: 'cat-roomba.gif' },
{ name: 'duck-shuffle.gif' },
{ name: 'monkey-on-a-pig.gif' }
]
},
{ name: 'TODO.md' }
];
</script>

<Folder name="Home" files={root} expanded/>

File.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export let name;
$: type = name.slice(name.lastIndexOf('.') + 1);
</script>

<style>
span {
padding: 0 0 0 1.5em;
background: 0 0.1em no-repeat;
background-size: 1em 1em;
}
</style>

<span style="background-image: url(tutorial/icons/{type}.svg)">{name}</span>

Folder.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<script>
import File from './File.svelte';

export let expanded = false;
export let name;
export let files;

function toggle() {
expanded = !expanded;
}
</script>

<span class:expanded on:click={toggle}>{name}</span>

{#if expanded}
<ul>
{#each files as file}
<li>
{#if file.files}
<!-- show folder -->
{:else}
<File {...file}/>
{/if}
</li>
{/each}
</ul>
{/if}

<style>
span {
padding: 0 0 0 1.5em;
background: url(/tutorial/icons/folder.svg) 0 0.1em no-repeat;
background-size: 1em 1em;
font-weight: bold;
cursor: pointer;
}

.expanded {
background-image: url(/tutorial/icons/folder-open.svg);
}

ul {
padding: 0.2em 0 0 0.5em;
margin: 0 0 0 0.5em;
list-style: none;
border-left: 1px solid #eee;
}

li {
padding: 0.2em 0;
}
</style>

它对于像tree view这样的多层数据结构很有用,其中文件夹可以包含其他文件夹。 在 Folder.svelte 我们希望能够做到这一点……

1
2
3
4
5
{#if file.files}
<Folder {...file}/>
{:else}
<File {...file}/>
{/if}

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

1
2
3
4
5
{#if file.type === 'folder'}
<svelte:self {...file}/>
{:else}
<File {...file}/>
{/if}

通过下面的例子来加深理解:点击Show me 进行对比

REPL

这个例子告诉我们<svelte:self>可以实现递归自身,方便大家在遇到tree view这样的组件的时候可以快速的完成,但是需要注意<svelte:self>使用的时候需要添加判断条件,否则可能会无限制的递归导致堆栈溢出。

二、<svelte:component>

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

日常工作中,我们采用react来写的时候也会有这样的情况,比如首页我们有BannerHotCardList模块,展示的顺序是按照服务端返回的数据结构来定,通过type来区分组件,你可以使用if或者switch来处理,我是这样实现的:

1
2
3
4
5
6
7
8
9
10
11
12
const componentMap = {
"banner":Banner,
"hot":Hot,
"card":Card
...
}

//

const commonElement = componentMap[type] || null
//
items.map((el,index)=>(<commonElement {...el} key={index}/>))

上述的代码就是不像使用if或者switch判断,但是react中没有<svelte:component>只能自己实现类似的功能。现在svelte提供的<svelte:component>就可以轻松的解决这个:

1
<svelte:component this={componentMap[type]}>

好了。咱们来看看例子:

App.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
import RedThing from './RedThing.svelte';
import GreenThing from './GreenThing.svelte';
import BlueThing from './BlueThing.svelte';

const options = [
{ color: 'red', component: RedThing },
{ color: 'green', component: GreenThing },
{ color: 'blue', component: BlueThing },
];

let selected = options[0];
</script>

<select bind:value={selected}>
{#each options as option}
<option value={option}>{option.color}</option>
{/each}
</select>

{#if selected.color === 'red'}
<RedThing/>
{:else if selected.color === 'green'}
<GreenThing/>
{:else if selected.color === 'blue'}
<BlueThing/>
{/if}

我们可以有一个动态组件:

1
<svelte:component this={selected.component}/>

this 值可以是任何组件构造函数,也可以是 falsy 值——如果是 falsy,则不渲染任何组件。

REPL

三、<svelte-element>

有时我们事先并不知道要渲染什么样的 DOM 元素。 <svelte:element> 在这里派上用场。 而不是一系列 if 块……

这个场景不是很多,例如你要写一个Button组件,你想这个组件也包含A链接,如果这个组件有url参数的话就DOM元素就是A,没有url就是Button,这里使用<svelte-element>就很简单了

咱们来看官方例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>

<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>

{#if selected === 'h1'}
<h1>I'm a h1 tag</h1>
{:else if selected === 'h3'}
<h3>I'm a h3 tag</h3>
{:else if selected === 'p'}
<p>I'm a p tag</p>
{/if}

根据selected的值进行判断输出。

我们可以有一个动态组件像下面一样:

1
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>

this 值可以是任何字符串,也可以是 falsy 值——如果是 falsy,则不渲染任何元素。

REPL

四、<svelte:window>

正如您可以将事件侦听器添加到任何 DOM 元素一样,您可以使用 <svelte:window> 将事件侦听器添加到窗口对象。

看下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
let key;
let keyCode;

function handleKeydown(event) {
key = event.key;
keyCode = event.keyCode;
}
</script>

<svelte:window/>

<div style="text-align: center">
{#if key}
<kbd>{key === ' ' ? 'Space' : key}</kbd>
<p>{keyCode}</p>
{:else}
<p>Focus this window and press any key</p>
{/if}
</div>

<style>
div {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
flex-direction: column;
}

kbd {
background-color: #eee;
border-radius: 4px;
font-size: 6em;
padding: 0.2em 0.5em;
border-top: 5px solid rgba(255, 255, 255, 0.5);
border-left: 5px solid rgba(255, 255, 255, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.2);
border-bottom: 5px solid rgba(0, 0, 0, 0.2);
color: #555;
}
</style>

在上面 <svelte:window/> ,添加 keydown 监听器:

1
<svelte:window on:keydown={handleKeydown}/>

DOM 元素一样,您可以添加诸如 preventDefault 之类的事件修饰符。

绑定

我们还可以绑定到窗口的某些属性,例如scrollY。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<script>
const layers = [0, 1, 2, 3, 4, 5, 6, 7, 8];

let y;
</script>

<svelte:window/>

<a class="parallax-container" href="https://www.firewatchgame.com">
{#each layers as layer}
<img
style="transform: translate(0,{-y * layer / (layers.length - 1)}px)"
src="https://www.firewatchgame.com/images/parallax/parallax{layer}.png"
alt="parallax layer {layer}"
>
{/each}
</a>

<div class="text">
<span style="opacity: {1 - Math.max(0, y / 40)}">
scroll down
</span>

<div class="foreground">
You have scrolled {y} pixels
</div>
</div>

<style>
.parallax-container {
position: fixed;
width: 2400px;
height: 712px;
left: 50%;
transform: translate(-50%,0);
}

.parallax-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
will-change: transform;
}

.parallax-container img:last-child::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: rgb(45,10,13);
}

.text {
position: relative;
width: 100%;
height: 300vh;
color: rgb(220,113,43);
text-align: center;
padding: 4em 0.5em 0.5em 0.5em;
box-sizing: border-box;
pointer-events: none;
}

span {
display: block;
font-size: 1em;
text-transform: uppercase;
will-change: transform, opacity;
}

.foreground {
position: absolute;
top: 711px;
left: 0;
width: 100%;
height: calc(100% - 712px);
background-color: rgb(32,0,1);
color: white;
padding: 50vh 0 0 0;
}

:global(body) {
margin: 0;
padding: 0;
background-color: rgb(253, 174, 51);
}
</style>

我们修改<svelte:window/>为下面的代码:

1
<svelte:window bind:scrollY={y}/>

可以绑定到的属性:

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • scrollX
  • scrollY
  • online window.navigator.onLine的别名。

除“scrollX”和“scrollY”外,其余的都是只读的。

REPL

五、<svelte:body>

<svelte:window>类似,该 <svelte:body>标签允许你添加事件监听document.body。 该标签与 mouseentermouseleave 事件一起使用, 不会触发 window事件。

看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
let hereKitty = false;

const handleMouseenter = () => hereKitty = true;
const handleMouseleave = () => hereKitty = false;
</script>

<style>
img {
position: absolute;
left: 0;
bottom: -60px;
transform: translate(-80%, 0) rotate(-30deg);
transform-origin: 100% 100%;
transition: transform 0.4s;
}

.curious {
transform: translate(-15%, 0) rotate(0deg);
}

:global(body) {
overflow: hidden;
}
</style>

<svelte:body/>

<!-- creative commons BY-NC http://www.pngall.com/kitten-png/download/7247 -->
<img
class:curious={hereKitty}
alt="Kitten wants to know what's going on"
src="tutorial/kitten.png"
>

mouseentermouseleave 函数添加到 <svelte:body>标签内:

1
2
3
4
<svelte:body
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
/>

REPL

六、<svelte:head>

<svelte:head> 允许你在页面的 <head> 标签内插入内容:

1
2
3
<svelte:head>
<link rel="stylesheet" href="/tutorial/dark-theme.css">
</svelte:head>

在服务器端渲染(SSR)模式下, <svelte:head> 中的内容单独返回到HTML中。

七、<svelte:options>

<svelte:options> 元素允许您指定编译器选项。
看例子:
App.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
import Todo from './Todo.svelte';

let todos = [
{ id: 1, done: true, text: 'wash the car' },
{ id: 2, done: false, text: 'take the dog for a walk' },
{ id: 3, done: false, text: 'mow the lawn' }
];

function toggle(toggled) {
todos = todos.map(todo => {
if (todo === toggled) {
// return a new object
return {
id: todo.id,
text: todo.text,
done: !todo.done
};
}

// return the same object
return todo;
});
}
</script>

<h2>Todos</h2>
{#each todos as todo}
<Todo {todo} on:click={() => toggle(todo)}/>
{/each}

Todo.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
import { afterUpdate } from 'svelte';
import flash from './flash.js';

export let todo;

let div;

afterUpdate(() => {
flash(div);
});
</script>

<style>
div {
cursor: pointer;
line-height: 1.5;
}
</style>

<!-- the text will flash red whenever
the `todo` object changes -->
<div bind:this={div} on:click>
{todo.done ? '👍': ''} {todo.text}
</div>

我们将使用 immutable 选项作为示例。 在这个应用程序中,<Todo> 组件在收到新数据时会闪烁。 单击其中一项会通过创建更新的 todos 数组来切换其完成状态。 这会导致其他 <Todo> 项目闪烁,即使它们最终不会对 DOM 进行任何更改。

我们可以设置<Todo> 组件,让其期望的数据是不可变(immutable) 的。 这意味着我们承诺永远不会对“todo”的属性进行“变更(mutate )”,而是在内容发生变化时重新创建新的todo对象。

将此代码添加到Todo.svelte 文件内顶部:

1
<svelte:options immutable={true}/>

您可以根据需要将其简写为 <svelte:options immutable/>

现在,当您通过单击todo来切换状态时,仅被更新的组件会闪烁:

该标签可设置的选项有:

  • immutable={true} :你不能使用可变数据,因此编译器可以通过引用简单的对比检查来确定值是否已更改。
  • immutable={false} :默认值。对于可变对象数据变更,Svelte将其保持不变。
  • accessors={true} :为组件的属性添加getter和setter。
  • accessors={false}:默认。
  • namespace="..." :将使用namespace的组件,最常见的是”svg”。
  • tag="..." :指定将此组件编译为自定义标签时使用的名称。
    有关这些选项的更多信息,请查阅 API reference

REPL

八、<svelte:fragment>

<svelte:fragment> 元素允许您将内容放置在命名槽中,而无需将其包装在容器 DOM 元素中。 这样可以保持文档的流程布局不变。类比学习,类似react <Fragment></Fragment>

看例子:
App.svelte

1
2
3
4
5
6
7
8
9
10
<script>
import Box from './Box.svelte'
</script>

<Box>
<div slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</div>
</Box>

Box.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="box">
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>

<style>
.box {
width: 300px;
border: 1px solid #aaa;
border-radius: 2px;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.1);
padding: 1em;
margin: 0 0 1em 0;

display: flex;
flex-direction: column;
gap: 1em;
}
</style>

在示例中,请注意我们是如何将间距为 1em 的 flex 布局应用到盒子上的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Box.svelte -->
<div class="box">
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>

<style>
.box {
display: flex;
flex-direction: column;
gap: 1em;
}
</style>

但是,页脚中的内容并没有按照这种节奏进行间隔,因为将其包装在 div 中创建了一个新的流布局。

我们可以通过改变 App 组件中的 <div slot="footer"> 来解决这个问题。 将 <div> 替换为 <svelte:fragment>

1
2
3
4
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>

这个就比较简单了,在类比下react的Fragment就清晰明了了。

总结

  • <svelte:self>不能出现在标签的顶层;它必须在if或each块内,以防止死循环。

  • <svelte:component> 标签动态渲染component,被指定的 component 具有一个 this 属性。每当标签上的属性发生变化,该 component 将会销毁并重新创建渲染。

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


Svelte 官方入门教程(14) - 特殊元素
http://yoursite.com/2022/07/26/svelte-tutorial-14/
作者
昂藏君子
发布于
2022年7月26日
许可协议