我正在参加「掘金·启航计划」
1. 搭建项目
npm create vite@latest myapp -- --template svelte
cd myapp
npm install
npm run dev
or SvelteKit
npm create svelte@latest my-app
cd my-app
npm install
npm run dev -- --open
2.svelte 基础知识
生命周期
- onMount: 组件挂载时调用。
<script>
import { onMount } from 'svelte';
let photos = [];
onMount(async () => {
const res = await fetch(`/tutorial/api/album`);
photos = await res.json();
});
</script>
- onDestroy: 组件销毁时执行。
- beforeUpdate: 在数据更新前执行。
- afterUpdate: 在数据更新完成后执行。
- tick: DOM元素更新完成后执行。
import { tick } from 'svelte';
let text = `Select some text and hit the tab key to toggle uppercase`;
async function handleKeydown(event) {
const { selectionStart, selectionEnd, value } = this;
await tick();
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
}
组件格式
# 在`.svelte`文件中,这三个部分( script, styles和元素标签)都是可选的。
<script>
// logic goes here
</script>
<!-- markup (zero or more items) goes here -->
<style>
/* styles go here */
</style>
'反应性(reactive)' 分配
# 由于Svelte的反应性是基于分配的,因此使用`.push()`和 `.splice()`和这样的数组方法不会自动触发更新。解决此问题的选项可以使用
- 解决该问题的一种方法是添加一个多余的赋值语句:
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
}
- 但还有一个更惯用的解决方案:
function addNumber() {
numbers = [...numbers, numbers.length + 1];
}
你可以使用类似的模式来替换 pop
、shift
、unshift
和 splice
方法
一个简单的经验法则是:被更新的变量的名称必须出现在赋值语句的左侧。例如下面这个:
const foo = obj.foo;
foo.bar = 'baz';
$:
:声明反应性,值改变的时候再次运行
当它们依赖的值发生更改时,它们都会在 component 更新之前立即运行。 这个功能有点像 Vue 的 computed。 $: 可以监听表达式内部的变化从而做出响应
<script>
let count = 1;
// the `$:` means 're-run whenever these values change'
// Svelte替你将 `let`插入声明
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Count: {count}
</button>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>
如果语句完全由未声明变量的赋值组成,则Svelte替你将
let
插入声明。
用 $
前缀来存储store值
<script>
import { writable } from 'svelte/store';
const count = writable(0);
console.log($count); // logs 0
count.set(1);
console.log($count); // logs 1
$count = 2;
console.log($count); // logs 2
</script>
一个store 必须包含一个
.subscribe
方法,该方法必须接受subscription 函数作为其参数
<script context="module">
一个
<script>
标签具有一个context="module"
熟悉,在模块首次 evaluates 时运行一次,而不是针对每个组件实例运行一次。 此块中声明的值可以在常规的<script>
代码块中访问 (和 component 的标签), 反之亦然。
你可以使用export
绑定到该块,它们将作为已编译模块导出。
你不能使用 export default
来绑定,因为组件本身就是默认导出(export default)。
带有
module
声明的<script>
内代码不具有反应性。虽然变量自身会更新,但重新分配它们不会触发重新渲染,对于多个组件之间共享的值,请考虑使用 store.
<script context="module">
let totalComponents = 0;
// 此处允许执行import操作,例如
// `import Example, { alertTotal } from './Example.svelte'`
export function alertTotal() {
alert(totalComponents);
}
</script>
<script>
totalComponents += 1;
console.log(`total number of times this component has been created: ${totalComponents}`);
</script>
style
<style>
标签块中的样式仅仅生效于component内部。
<style>
p {
/* 这只会影响此组件中的<p>标签 */
color: burlywood;
}
</style>
:global(...)
使用
:global(...)
修饰符来添加全局样式
<style>
:global(body) {
/* 这里样式全局应用于 <body>内都 */
margin: 0;
}
div :global(strong) {
/* 这里表示全局应用于被<div>包裹的<strong>标签 */
color: goldenrod;
}
</style>
模板语法
- 标签
小写标签诸如
<div>
之类,表示常规的HTML标签,大写标签例如<Widget>
或<Namespace.Widget>
,表示这是一个 component。
<script>
import Widget from './Widget.svelte';
</script>
<div>
<Widget/>
</div>
- props 属性
# 默认情况下,属性的使用方式与HTML属性的使用方法一样。
<div class="foo">
<button disabled>can't touch this</button>
</div>
# 属性值也可以去掉引号
<input type=checkbox>
# 属性值可以包含JavaScript表达式
<a href="page/{p}">page {p}</a>
#JavaScript表达式,表达式可能会包含导致常规HTML语法高亮失效,使之不能正常显示语法高亮,因此有必要使用引号来避免此情况
<button disabled={!clickable}>...</button>
<button disabled="{number !== 42}">...</button>
# 当出现属性名和值一样时 (`name={name}`),可以简写为`{name}`
<!-- 两者是等价的 -->
<button disabled={disabled}>...</button>
<button {disabled}>...</button>
# *`$$props`* 可以传递所有 props 属性到一个 component 中,包含未使用`export`声明的 props 属性。它在特殊情况下很有用,但通常不推荐使用,因为Svelte难以优化
<Widget {...$$props}/>
-
文本表达式
-
注释
-
{#if ...}
条件渲染
{#if answer === 42}
<p>what was the question?</p>
{/if}
{#if porridge.temperature > 100}
<p>too hot!</p>
{:else if 80 > porridge.temperature}
<p>too cold!</p>
{:else}
<p>just right!</p>
{/if}
{#each ...}
列表渲染
<ul>
{#each items as item}
<li>{item.name} x {item.qty}</li>
{/each}
</ul>
# 空数据: 是空数组的情况下,还可以组合 {:else} 一起使用。
{#each todos as todo}
<p>{todo.text}</p>
{:else}
<p>No tasks today!</p>
{/each}
{#await ...}
异步渲染
{#await promise}
<!-- promise is pending -->
<p>waiting for the promise to resolve...</p>
{:then value}
<!-- promise was fulfilled -->
<p>The value is {value}</p>
{:catch error}
<!-- promise was rejected -->
<p>Something went wrong: {error.message}</p>
{/await}
{@html ...} HTML
内容插入
在 Vue 中有
v-html
方法,它可以将 HTML 标签渲染出来。在 Svelte 中也有这个方法,在插值前面使用@html
标记一下即可 但此方法有可能遭受 XSS 攻击
<div class="blog-post">
<h1>{post.title}</h1>
{@html post.content}
</div>
{@debug ...}
调试模式
<script>
let user = {
firstname: 'Ada',
lastname: 'Lovelace'
};
</script>
{@debug user}
<h1>Hello {user.firstname}!</h1>
- @const
注意必须放在
{#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <Component /> or <svelte:fragment />
里
<script>
export let boxes;
</script>
{#each boxes as box}
{@const area = box.width * box.height}
{box.width} * {box.height} = {area}
{/each}
{@const} is only allowed as direct child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <Component /> or <svelte:fragment />.
样式
<div style:color={myColor}>...</div>
<div style="color: blue;" style:color="red">This will be red</div>
<div class="{active ? 'active' : ''}">...</div>
<div class:active={active}>...</div>
<!-- Shorthand, for when name and value match -->
<div class:active>...</div>
在 HTML 里可以使用 class:xxx 动态设置要激活的类。这里的 xxx 是对应的类名。
语法是 class:xxx={state} ,当 state 为 true 时,这个样式就会被激活使用
<button
class:selected="{current === 'bar'}"
on:click="{() => current = 'bar'}"
>bar</button>
事件
on:eventname={handler}
on:eventname|modifiers={handler}
<script>
let count = 0;
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
modifiers(事件修饰符)以下这些修饰符可以用:
on:click|once|capture={...}
preventDefault :禁止默认事件。在程序运行之前调用 event.preventDefault()
stopPropagation :调用 event.stopPropagation(), 防止事件到达下一个标签
passive :改善了 touch/wheel 事件的滚动表现(Svelte会在合适的地方自动加上它)
capture:表示在 capture阶段而不是bubbling触发其程序
once :程序运行一次后删除自身
self:点击自身触发
trusted:
数据绑定
<!-- These are equivalent -->
<input bind:value={value}>
<input bind:value> // 简写,bind:value 绑定的属性是 value ,而在 JS 中声明的变量名也叫 value ,此时就可以使用简写的方式
# 单选框和复选框
<script>
let tortilla = 'Plain';
let fillings = [];
</script>
<!-- grouped radio inputs are mutually exclusive -->
<input type="radio" bind:group={tortilla} value="Plain">
<input type="radio" bind:group={tortilla} value="Whole wheat">
<input type="radio" bind:group={tortilla} value="Spinach">
<!-- grouped checkbox inputs populate an array -->
<input type="checkbox" bind:group={fillings} value="Rice">
<input type="checkbox" bind:group={fillings} value="Beans">
<input type="checkbox" bind:group={fillings} value="Cheese">
<input type="checkbox" bind:group={fillings} value="Guac (extra)">
bind:this
针对传统的DOM节点,请使用 bind:this来绑定
<script>
import { onMount } from 'svelte';
let canvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
drawStuff(ctx);
});
</script>
<canvas bind:this={canvasElement}></canvas>
use:action use 指令
其本质是元素上的生命周期函数。它们可用于譬如以下几个方面:
- 与第三方库对接
- 延迟加载图片
- 工具提示(tooltips)
- 添加自定义事件处理程序
<script>
import { clickOutside } from "./click_outside.js";
let showModal = true;
</script>
<button on:click={() => (showModal = true)}>Show Modal</button>
{#if showModal}
<div class="box" use:clickOutside on:outclick={() => (showModal = false)}>
Click outside me!
</div>
{/if}
<style>
.box {
--width: 100px;
--height: 100px;
position: absolute;
width: var(--width);
height: var(--height);
left: calc(50% - var(--width) / 2);
top: calc(50% - var(--height) / 2);
display: flex;
align-items: center;
padding: 8px;
border-radius: 4px;
background-color: #ff3e00;
color: #fff;
text-align: center;
font-weight: bold;
}
</style>
组件通讯
- 父组件传数据到子组件
# 父组件
<script>
import Child from './Child.svelte'
</script>
<div>子组件 Child 的内容:</div>
<Child number="123456" />
# 子组件
<script>
export let number = '666'
</script>
<div>data:{number}</div>
- 子组件转数据到父组件:createEventDispatcher
# 子组件 child
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function print() {
dispatch('printPhone', '13288888888')
}
</script>
<button on:click={printData}>传递数据</button>
# 父组件
<script>
import Child from './Child.svelte'
function print(data) {
console.log(`数据: ${data.detail}`)
}
</script>
<div>子组件 Phone 的内容:</div>
<Phone on:printData={print} />
插槽
在子组件中使用
<slot>
标签,可以接收父组件传进来的HTML 内容
# 子组件
<div class="box">
<slot></slot>
</div>
# 父组件
<script>
import Box from './Box.svelte';
</script>
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
3.路由
install
npm install svelte-spa-router
使用
# routes.js
// Import the wrap method
import {wrap} from 'svelte-spa-router/wrap'
// Note that Author and Book are not imported here anymore, so they can be imported at runtime
import Home from './routes/Home.svelte'
import NotFound from './routes/NotFound.svelte'
const routes = {
'/': Home,
// Wrapping the Author component
'/author/:first/:last?': wrap({
asyncComponent: () => import('./routes/Author.svelte')
}),
// Wrapping the Book component
'/book/*': wrap({
asyncComponent: () => import('./routes/Book.svelte')
}),
// Catch-all route last
'*': NotFound,
}
export default routes;
# App.svelte
import Router from 'svelte-spa-router'
import routes from './routes';
<main>
<Router {routes} />
</main>
# 切换路由
<a href="#/book/123">#/book/123</a>
or
<script>
import { link } from 'svelte-spa-router';
const path = '/about/wise';
</script>
<a use:link={path}>To About Page</a>
# 获取路由参数,在对应路由页
>对于动态路由,如果在组件中以 props 的形式导出了一个 params ,通过 /#/book/123 访问 Detail 组件
<script>
export let params = {};
console.log(params);
</script>
如果路由上还携带有 query 参数(比如:/#/book/123?name=wise&blog=cnblogs)
可以通过 svelte-spa-router 提供的 querystring 拿到
import {location, querystring} from 'svelte-spa-router'
<div>book</div>
<div>#/book/123?a=1&b=2 获取参数</div>
<div>{$location}</div>
<div>{$querystring}</div>
<script>
export let params = {};
console.log(params); // book/123---> {wild: 123}
import {location, querystring} from 'svelte-spa-router'
// 只读的store 要使用 $ 读取
console.log($location, $querystring)
</script>
4.store使用
# store.js
import { writable } from 'svelte/store';
export const count = writable(0);
# App.svelte
<script>
import { count } from './stores.js';
// 只读的store 要使用 $ 读取
</script>
<h1>The count is {$count}</h1>
通过 set 和 update 设置数据更新. 通过 subscribe 订阅值的变化
# App.svelte
<script>
import { count } from './stores.js';
import Incrementer from './Incrementer.svelte';
import Resetter from './Resetter.svelte';
let countValue;
count.subscribe(value => {
countValue = value;
});
</script>
<h1>The count is {countValue}</h1>
<Incrementer/>
<Resetter/>
# Incrementer.svelte
<script>
import { count } from './stores.js';
function increment() {
count.update(n => n + 1);
}
</script>
<button on:click={increment}>
+
</button>
# Resetter.svelte
<script>
import { count } from './stores.js';
function reset() {
count.set(0);
}
</script>
<button on:click={reset}>
reset
</button>
# stores.js
import { writable } from 'svelte/store';
export const count = writable(0);