关于学习 [[Svelte]] Tutorial 的简要笔记。
Introduction
Svelte 组件通过 .svelte
文件后缀表示,一个组件可以包含三个部分: script(脚本), styles(样式) 和 markup(HTML),三个部分都不是必须。
如果想在 [[HTML]] 中使用变量,可以花括号 {}
包裹变量名实现:
<script lang="ts"> let name = "immwind" let src = "image.gif"</script>
<h1>Hello {name}!</h1>
调用 [[JavaScript]] 的内置对象也同样如此,比如使用 [[toUpperCase]] 方法:
<h1>Hello {name.toUpperCase()}!</h1>
还有元素属性也支持:
<img src={src} alt="image" />
另外在属性名和值相同的情况下,可以使用简写的方式省略属性名:
<img {src} alt="image" />
样式
和在 HTML 中一样在组件中添加 style
标签添加样式(仅在当前组件生效):
<p>This is a paragraph.</p>
<style> p { color: goldenrod; font-family: "Comic Sans MS", cursive; font-size: 2em; }</style>
组件
组件可以通过 import name from .
的形式导入,然后使用 <name />
的形式调用:
<script lang="ts"> import Nested from './Nested.svelte'</script>
<p>This is a paragraph.</p><Nested />
<style>p { color: goldenrod; font-family: 'Comic Sans MS', cursive; font-size: 2em;}</style>
另外组件名称建议首字母大写,以和 HTML 元素区分。
插入 HTML
通过 [[{@html…}]] 标记可以插入 HTML 语句:
<script> let string = `this string contains some <strong>HTML!!!</strong>`;</script>
<p>{string}</p> // this string contains some <strong>HTML!!!</strong><p>{@html string}</p> // this string contains some HTML!!!
注:确保 @html
中的字符串是安全的,否则可能会导致 XSS 攻击。
Reactivity
如果希望在 js 文件中使用响应式的方法,可以在文件名中加上 .svelte
后缀,然后通过 import
导入,比如把 utils.js
改成 utils.svelte.js
,然后通过 import
导入。
$state
[[$state(…)]] 是 Svelte 中的 rune,当状态值改变时,会自动更新视图:
<script> let count = $state(0);
function increment() { count += 1 }</script>
<button onclick={increment}> Clicked {count} times.</button>
深度响应,可以响应值的更新,比如下面例子中是通过 push
方法更新数组,同样会触发响应更新:
<script> let numbers = $state([1, 2, 3, 4]);
function addNumber() { numbers.push(numbers.length + 1) }</script>
<p>{numbers.join(' + ')} = ...</p>
<button onclick={addNumber}> Add a number</button>
$derived
[[$derived(…)]] 从其他状态中派生状态,只读;当依赖的状态值改变时,会自动更新派生状态值:
<script> let numbers = $state([1, 2, 3, 4]); let total = $derived(numbers.reduce((t, n) => t + n, 2))
function addNumber() { numbers.push(numbers.length + 1); }</script>
<p>{numbers.join(' + ')} = {total}</p>
<button onclick={addNumber}> Add a number</button>
在上面的例子中,当 numbers
更新时,total
会同样会自动更新。
$inspect
[[$inspect(…)]] 可以实时打印出状态值的变更,仅在开发环境有效,生产环境不会有任何操作:
<script> let count = $state(0);</script>
<button onclick={() => count += 1}> Clicked {count} times.</button>
<p>count: {$inspect(count)}</p>
在开发时如果需要用到 console.log
打印变量,可以尝试使用 $inspect
代替,还支持通过 .with(fn)
的方式追踪变化是从哪里开始的:
$inspect(numbers).with(console.trace);
$effect
和 $inspect
类似,[[$effect(…)]] 可以监听状态值的变更,但是不同的是 $effect
还支持对变化进行响应:
<script lang="ts"> let count = $state(0);
$effect(() => { console.log(count); // 打印日志 document.title = `计数: ${count}`; // 比如修改DOM });</script>
<button onclick={() => count += 1}> Click Add {count}</button>
在上面的例子中,当 count
更新时,会自动调用 $effect
中的回调函数,打印日志和修改 DOM。
Props
在组件中传递数据,可以使用 [[$props(…)]] rune 接收属性,比如下面是接收 anser 属性:
<script> let { answer } = $props();</script>
<p>The answer is {answer}</p>
在引用的一方传递对应属性的值:
<script> import Nested from './Nested.svelte';</script>
<Nested answer={42} />
设置默认值:
<script> let { answer = "a mystery" } = $props();</script>
设置后如果没有传递值,则使用默认值:
<Nested answer={42} /><Nested answer />
同样也可以设置多个属性:
<script> let { name, version, description, website } = $props();</script>
有多个属性的情况下,传递值时可以直接展开:
<script> import PackageInfo from './PackageInfo.svelte';
const pkg = { name: 'svelte', version: 5, description: 'blazing fast', website: 'https://svelte.dev' };</script>
<PackageInfo {...pkg} />
或者是通过解构的方式:
<script> let stuff = $props();</script>
然后通过 stuff.name
方式调用。
Logic
{#...}
: 开启块{:...}
: 继续块{/...}
: 关闭块
if
[[if]] 条件语句。
if:
{#if count > 10 } <p>{count} is greater than 10</p>{/if}
if..else:
{#if count > 10} <p>{count} is greater than 10</p>{:else} <p>{count} is between 0 and 10</p>{/if}
else if:
{#if count > 10} <p>{count} is greater than 10</p>{:else if count < 5} <p>{count} is less than 5</p>{:else} <p>{count} is between 0 and 10</p>{/if}
each
通过 [[{]] 可以在模板中遍历数组,以减少重复代码:
<script> const colors = ['red', 'orange', 'yellow', 'green']; let selected = $state(colors[0]);</script>
<h1 style="color: {selected}">Pick a colour}</h1>
<div> {#each colors as color, i} <button style="background: {color}" aria-label={color} aria-current={selected === color} onclick={() => { selected = color; }} >{i + 1}</button> {/each}</div>
可以为 each
每次迭代指定一个唯一的键:
<script> import Thing from './Thing.svelte';
let things = $state([ { id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'carrot' }, { id: 4, name: 'doughnut' }, { id: 5, name: 'egg' } ]);</script>
<button onclick={() => things.shift()}> Remove first thing</button>
{#each things as thing (thing.id)} <Thing name={thing.name} />{/each}
wait
语法:
#await
…:then
…:catch
…/await
当需要 [[{]] 等待异步操作完成时,可以使用 #await
语句:
<script> import { roll } from './utils.js';
let promise = $state(roll());</script>
<button onclick={() => promise = roll()}> roll the dice</button>
{#await promise} <p>...rolling</p>{:then number} <p>you rolled a {number}!</p>{:catch error} <p style="color: red">{error.message}</p>{/await}
await 和 catch 都可以省略:
{#await promise then number} <p>you rolled a {number}!</p>{/await}