上一讲是上下文的相关知识,本讲说下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} {: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来写的时候也会有这样的情况,比如首页我们有Banner
、Hot
、Card
、List
模块,展示的顺序是按照服务端返回的数据结构来定,通过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
。 该标签与 mouseenter
和 mouseleave
事件一起使用, 不会触发 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 /> <img class:curious={hereKitty} alt="Kitten wants to know what's going on" src="tutorial/kitten.png" >
将 mouseenter
和 mouseleave
函数添加到 <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 { id: todo.id, text: todo.text, done: !todo.done }; } 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 > <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 <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,这两个还支持赋值,它可以使编写这方面的程序变得更简单。