初步了解
Svelte 是一个构建 web 应用程序的工具。Svelte 与诸如 React 和 Vue 等 JavaScript 框架类似,都怀揣着一颗让构建交互式用户界面变得更容易的心。
但是有一个关键的区别:Svelte 在 构建/编译阶段 将你的应用程序转换为理想的 JavaScript 应用,而不是在 运行阶段 解释应用程序的代码。这意味着你不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。
你可以使用 Svelte 构建整个应用程序,也可以逐步将其融合到现有的代码中。你还可以将组件作为独立的包(package)交付到任何地方,并且不会有传统框架所带来的额外开销。
基础知识
声明式添加数据
在组件<script>
标签声明一个 name
变量,然后在标签中应用name
变量:
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
动态属性
就像可以使用花括号控制文本一样,也可以使用花括号控制元素属性。
<script>
let imageSrc = 'image.jpg';
</script>
<img src={imageSrc} alt="Super Man">
如果变量名与属性名相同,可以省去属性名,否则会报错
<script>
let src = 'image.jpg';
</script>
// <img src={src} alt="Super Man"> //A11y: <img> element should have an alt attribute
<img {src} alt="Super Man">
同样属性内部也可以添加变量应用
<script>
let src = 'image.jpg';
let name = 'You';
</script>
<img {src} alt="Super Man is { name }">
Css样式
组件的Css样式规则的作用域会被限定在当前组件中,其原理是往标签添加一个class类,类似Css Scope。
<style>
#header{
color: green;
}
.main {
width: 50px;
height: 50px;
background: yellow;
}
p{
color: red
}
</style>
<span id="header">Header</span>
<div class="main"></div>
<p>This is a paragraph.</p>
最终css转化成:
#header.svelte-1vottuu{color:green}
.main.svelte-1vottuu{width:50px;height:50px;background:yellow}
p.svelte-1vottuu{color:red}
这样就达到作用域的效果啦。
嵌套组件
跟Vue和React一样,都会根据使用场景将应用程序拆分出不同类型的组件。组件导入在<script>
标签中使用import语法导入
// Comp.svelte
<p>This is a Component.</p>
// App.svelte
<script>
import Comp from './Comp.svelte'
</script>
<p>This is a App.</p>
<Comp />
需要注意的是,组件名称 Nested 的首字母大写。采用此约定是为了使我们能够区分用户自定义的组件和一般的 HTML 标签。
HTML 标签
通常,字符串以纯文本形式插入,这意味着像 <
和 >
这样的字符没有特殊的含义。但有时需要将 HTML 直接绘制到组件中。这时候可以使用特殊标记{@html ...}
实现:
<script>
let string = `This is a <strong>super man</strong>.`
</script>
<p>{@html string}</p>
在将表达式的输出插入到 DOM 之前,Svelte 不会对
{@html ...}
内的表达式的输出做任何清理的。换言之,如果使用此功能,则必须手动转义来自不信任源的 HTML 代码,否则会使用户面临 XSS 攻击的风险。
Reactivity 数据响应
赋值
Svelete 的内核是一个强大的 (反应性)reactivity 系统,能够让 DOM 与你的应用程序状态保持同步。
<script>
let count = 0;
function handleClick() {
count +=1
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
响应式声明 - 值
类似Vue的computed
,需要通过其他组件状态计算得到。
<script>
let count = 0;
$: doubled = count * 2;
</script>
<p>{count} doubled is {doubled}</p>
响应式声明 - 语句
类型Vue的watch
, 监听某个变量的变化
$: console.log(`the count is ${count}`);
$: {
console.log(`the count is ${count}`);
alert(`I SAID THE COUNT IS ${count}`);
}
$: if (count >= 10) {
alert(`count is dangerously high!`);
count = 9;
}
更新数组和对象
由于 Svelte 的反应性是由赋值语句触发的,因此使用数组的诸如 push 和 splice 之类的方法就不会触发自动更新。
如下操作不会触发任何变化:
function addNumber() {
numbers.push(numbers.length + 1);
}
解决该问题的一种方法是添加一个多余的赋值语句:
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
}
// OR
function addNumber() {
numbers = [...numbers, numbers.length + 1];
}
一个简单的经验法则是:被更新的变量的名称必须出现在赋值语句的左侧。
属性
属性定义
组件通过export
声明props, 也可直接给props赋值
// Child.svelte
<script>
export let answer;
export let name = "Tony"
</script>
// App.svelte
<script>
import Child from './Child.svelte';
</script>
<Child amswer="a mystery" name="Judy" />
属性传递
可以利用拓展符把对象属性添加到组件中
<script>
// <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>
<Info {...pkg} />
</script>
逻辑
条件逻辑
通过把html代码放到if块中来控制渲染逻辑
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
{#if !user.loggedIn}
<button on:click={toggle}>
Log in
</button>
{/if}
也可以直接写成 if ... else ...
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}
也可以直接写成 if ... else if ...
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}
循环逻辑
遍历数据列表使用each
块
<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 作为第二个参数(key),类似于:
{#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}
如果你希望代码更加健壮,你可以使用each cats as { id, name }
来解构,用cat.id
和 cat.name
来代替 id
和 name
。
{#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}
each块添加key值
你可以将任何对象用作 key 来使用,就像Svelte 用
Map
在内部作为key一样,换句话说,你可以用(thing)
来代替(thing.id)
作为 key 值。但是,使用字符串或者数字作为 key 值通常更安全,因为这能确保它的唯一性。
{#each things as thing (thing.id)}
<Thing current={thing.color}/>
{/each}
Await 块
很多Web应用程序都可能在某个时候有需要处理异步数据的需求。使用 Svelte 在标签中使用 await 处理promises 数据亦是十分容易:
<script>
const promise = getData();
async function getData(){
const res = await axios.get(...)
// TODO
return res
}
</script>
{#await promise}
<p>...waiting</p>
{:then number}
<p>The number is {number}</p>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
如果在请求完成之前不想程序执行任何操作,也可以忽略第一个块。
{#await promise then value}
<p>the value is {value}</p>
{/await}
事件
通过在标签添加on:event
来添加事件
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>
添加内联函数
在某些框架中,出于性能原因,您可能会看到避免内联事件处理程序的建议,特别是在循环内部。这个建议不适用于Svelte——无论您选择哪种形式,编译器都会正常运行。
<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
The mouse position is {m.x} x {m.y}
</div>
事件修饰符
DOM 事件处理程序具有额外的 修饰符(modifiers) 。例如,带 once
修饰符的事件处理程序只运用一次:
<script>
function handleClick() {
alert('no more alerts')
}
</script>
<button on:click|once={handleClick}>
Click me
</button>
所有修饰符列表:
preventDefault
:调用event.preventDefault()
,在运行处理程序之前调用。比如,对客户端表单处理有用。stopPropagation
:调用event.stopPropagation()
, 防止事件影响到下一个元素。passive
: 优化了对 touch/wheel 事件的滚动表现(Svelte 会在合适的地方自动添加滚动条)。capture
— 在 capture 阶段而不是bubbling 阶段触发事件处理程序 (MDN docs)once
:运行一次事件处理程序后将其删除。self
— 仅当 event.target 是其本身时才执行。
你可以将修饰符组合在一起使用,例如:on:click|once|capture={...}
。
组件事件
组件也可以调度事件,为此,组件内必须创建一个相同事件并在外部进行分配。
// Inner.svelte
<script>
// setup code goes here
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher()
function sayHello() {
dispatch('message',{
text: 'Hello123!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
// App.svelte
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>
createEventDispatcher
必须在首次实例化组件时调用它,—组件本身不支持如setTimeout
之类的事件回调。 定义一个dispatch
进行连接,进而把组件实例化。
事件转发
与 DOM 事件不同, 组件事件不会 冒泡(bubble) ,如果你想要在某个深层嵌套的组件上监听事件,则中间组件必须 转发(forward) 该事件。
Svelte 设立了一个简写属性 on:message
。 message
没有赋予特定的值得情况下意味着转发所有massage
事件:
// App.svelte
<Outer on:message={handleMessage} />
// Outer svelte
<Inner on:message />
// Inner.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
DOM事件转发
在子组件<button>
标签添加click
事件,就可以接收到外部的handleClick()事件。
<button on:click>
Click me
</button>
Binding
input
通过bind:value
来绑定变量
<script>
let name = 'world';
</script>
<input bind:value={name}>
<input bind:value={name}>
<h1>Hello {name}!</h1>
在input使用数字处理方法
<input type=number bind:value={a} min=0 max=10>
<input type=range bind:value={a} min=0 max=10>
复选框
我们不仅可以使用input.value
,也可以将复选状态绑定input.checked
将复选框的状态绑定:
<input type=checkbox bind:checked={yes}>
输入框组绑定
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<h2>Flavours</h2>
<label>
<input type=checkbox bind:group={flavours} value="Cookies and cream">
Cookies and cream
</label>
<label>
<input type=checkbox bind:group={flavours} value="Mint choc chip">
Mint choc chip
</label>
<label>
<input type=checkbox bind:group={flavours} value="Raspberry ripple">
Raspberry ripple
</label>
文本域 textarea
<textarea bind:value={value}></textarea>
{@html marked(value)}
选项框
利用 bind:value
对 <select>
标签进行绑定,即使 <option>
中的值是对象而非字符串, Svelte 对它进行绑定也不会有任何困难。
<script>
const datas = [
{
id: 1,
value: "data1",
},
{
id: 2,
value: "data2",
},
{
id: 3,
value: "data3",
},
];
let selected;
</script>
<select bind:value={selected}>
{#each datas as data}
<option value={data}>{data.value}</option>
{/each}
</select>
<p>{selected.id} -- {selected.value}</p> // Error: Cannot read property 'id' of undefined
由于我们没有
selected
设置为初始值,因此绑定会自动将其(列表中的第一个)设置为默认值。 但也要注意,在绑定的目标未初始化前,selected
仍然是未定义的,因此我们应该谨慎的使用诸如selected.id
中的内容。
如果选择框含有一个名为 multiple
的属性,在这种情况下,它将会被设置为数组而不是单值。
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}
<select multiple bind:value={flavours}>
{#each menu as flavour}
<option value={flavour}>{flavour}</option>
{/each}
</select>
Contenteditable
支持 contenteditable="true"
属性的标签,可以使用 textContent
与 innerHTML
属性的绑定:
<script>
let html = '<p>Write some text!</p>';
</script>
<div contenteditable="true" bind:innerHTML={html}></div>
<pre>{@html html}</pre>
Each 块绑定
以TODO List为例
<script>
let todos = [
{ done: false, text: "1" },
{ done: false, text: "2" },
{ done: false, text: "3" },
];
function removeElement(index) {
todos.splice(index, 1);
todos = todos;
}
function addElement() {
// todos = [...todos, {done: false, text: ''}]
todos = todos.concat({ done: false, text: "" });
}
function clear() {
todos = todos.filter((el) => !el.done);
}
</script>
{#each todos as todo, index}
<div class:done={todo.done}>
<input type="checkbox" bind:checked={todo.done} />
<input type="text" disabled={todo.done} bind:value={todo.text} />
<button on:click={() => removeElement(index)}>删除</button>
</div>
{/each}
<button on:click={addElement}>Add New</button>
<button on:click={clear}>Remove Complete</button>
<style>
.done {
opacity: 0.4;
}
</style>
媒体标签的绑定
<audio>
和 <video>
同样支持部分的属性绑定,接下来演示其中几个属性。
<video
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}
bind:duration
bind:paused
></video>
bind:duration
相当于bind:duration={duration}
现在,当您单击视频时,它将视情况更新 time
、duration
和 paused
属性的值。这意味着我们可以使用它们来创建自定义控件。
通常,在网页中, 你会将
currentTime
用于对timeupdate
的事件监听并跟踪。但是这些事件很少触发,导致UI不稳定。 Svelte 使用currentTime
对requestAnimationFrame
进行查验,进而避免了此问题。
针对 <audio>
和 <video>
的 6 个readonly 属性绑定 :
duration
(readonly) :视频的总时长,以秒为单位。buffered
(readonly) :数组{start, end}
的对象。seekable
(readonly) :同上。played
(readonly) :同上。seeking
(readonly) :布尔值。ended
(readonly) :布尔值。
...以及 4 个双向 绑定:
currentTime
:视频中的当前点,以秒为单位。playbackRate
:播放视频的倍速,1
为 '正常'。paused
:暂停。volume
:音量,0到1之间的值。
<video>
还有多出了具有readonly 属性videoWidth
和videoHeight
属性的绑定。
尺寸绑定
每个块级标签都可以对 clientWidth
、clientHeight
、offsetWidth
以及 offsetHeight
属性进行绑定:
<div bind:clientWidth={w} bind:clientHeight={h}>
<span style="font-size: {size}px">{text}</span>
</div>
这些绑定是只读的,更改
w
和h
的值不会有任何效果。
对标签的尺寸更改请阅读这里。 由于涉及到额外的性能开销,因此不建议在页面中大量的使用。
使用display: inline
的标签无法获得尺寸,当然包含有其他有尺寸的标签 (例如<canvas>
)也不会得到正常显示。在这种情况下建议对该标签嵌套一层标签或者直接绑定它的父级。
this
this
可以绑定到任何标签 (或组件) 并允许你获取对渲染标签的引用。 例如,我们对<canvas>
标签进行绑定:
<script>
import { onMount } from 'svelte';
let canvas;
onMount(() => {
const ctx = canvas.getContext('2d');
ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,150,75);
});
</script>
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>
注意,canvas
的值 undefined
会一直存在直至组件挂载完毕,因此我们给 onMount
添加一个 生命周期函数来处理。
组件绑定
正如可以绑定到DOM元素的属性一样,你也可以将组件的属性绑定。例如,我们能绑定位于<Sinput>
组件内的 value
属性,就如同一个表单标签一般:
// App.svelte
<script>
import Sinput from "./Sinput.svelte"
let value = 1;
</script>
<Sinput bind:value={value}></Sinput>
<div>
{value}
</div>
// Sinput.svelte
<script>
export let value;
</script>
<input type="text" bind:value={value}>
总结
感觉svelte和react语法非常像,比如赋值什么的虽然react使用state方法添加响应值而svelte则是通过赋值的方式触发响应,不过svelte没有批处理,这样如果程序大时就会降低性能。接下来就是Runtime部分,虽然Svelte说没有Runtime,但是其中包含有生命周期等属性,这些都是属于Runtime的部分。