一. 简介
svelte类似于Vue,是一门纯前端框架.区别于Vue/React,用svelte的话说就是页面视图的更新并不是采用类似虚拟DOM的方式.另外传统的前端框架一般直接返回js文件由浏览器编译成页面视图,而svelte编译视图的过程则在构建编译时完成,大大减轻了浏览器的负担.
原文: Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
本文主要是对svelte官网中的教程进行翻译,以便快速了解svelte的一些基础知识
初始化项目
npm create svelte@latest myapp
cd myapp
npm install
npm run dev
建议使用Vite/Vite-plugin-svelte构建工具, 具体可了解(https://sveltesociety.dev/tools)
SvelteKit是用来专门构建svelte应用的脚手架(项目构建工具), 但目前正在开发中, 并不建议使用
与Vue相类似,svelte的组件文件是名为.svelte
结尾的文件
注意的是,组件的导入并不需要额外注册即可使用, 类似于Vue3的setup语法糖
二. 语法
2.1 页面赋值
定义一个变量, 在视图中用 {}
进行赋值
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
2.2 动态属性
与React类似, 直接对属性对应的值进行{}
赋值
<script>
let src = '/tutorial/image.gif';
</script>
<img src={src}>
但与其他框架不同的是, svelte此时会提示:
A11y: <img> element should have an alt attribute
值得注意的是, 如果属性名与变量名相同是, 可以采取省略简写:
<img {src} alt="A man dances.">
2.3 组件样式
与Vue类似, 组件样式直接在组件文件内部进行定义
<p>This is a paragraph.</p>
<style>
p {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
组件样式默认包含Scope作用域
2.4 html赋值
跟Vue和React相同, 采用{var}
进行视图赋值, 无法直接写入html内容, 如果需要, 则需要使用 @html
修饰符
<p>{@html string}</p>
也可以在html元素中设置contenteditable="true"
, bind:innerHTML
对HTML文本赋值
<div
contenteditable="true"
bind:innerHTML={html}
></div>
2.5 盒子元素属性
每一元素都有clientWidth
, clientHeight
, offsetWidth
, offsetHeight
属性,
每个属性都是只读的, 就算绑定的值发生变化, 对应的视图也不会发生变化
2.6 元素引用
于Vue中的ref属性类似,可以通过绑定this属性来获取绑定的对应元素
<script>
import { onMount } from 'svelte';
let canvas;
onMount(() => {
// 此时canvas对象相当于 document.querySelector("canvas")的DOM元素
const ctx = canvas.getContext('2d');
}
</script>
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>
当绑定对象是一个组件时,获取到的就是这一组件的实例
三. 事件
3.1 事件监听
在DOM元素上, on:event="{function}"
, eg:
<script>
let count = 0;
function incrementCount() {
count += 1;
}
</script>
<button on:click={incrementCount}>
3.2 派生属性(计算属性)
使用 $: var;
修饰, eg:
<script>
let count = 0;
$: doubled = count * 2;
</script>
此时计算属性对应的变量也具有响应式渲染视图的功能,但是该方法不止用定义响应数据的功能,也可实现类似watch属性.
eg:
<script>
let count = 0;
$: {
console.log('the count is ' + count);
alert('I SAID THE COUNT IS ' + count);
}
</script>
此时当count变量值发生变化时, 就是触发 $:
定义好方法执行, 当然也可以通过if判断来进行条件执行
$: if (count >= 10) {
alert('count is dangerously high!');
count = 9;
}
注意: 响应式变量的更新只能通过给变量赋值来触发更新。也就是说
let obj = { a: 1, b: 2, c: 3, d: 4 };
!func() {
obj.a = 2;
}()
<h2>obj.a是 {obj.a}</h2>
此时视图中显示的值还是1,即只修改对象内部的值并不会触发更改, 而且
let obj = { a: 1, b: 2, c: 3, d: 4 };
!func() {
obj.a = 2;
}()
obj = obj
这种赋值方式也是不行的
3.3 事件修饰符
如果只想监听一次DOM的点击事件, 可以使用|修饰符
on:click|once={function}
, 其他修饰符与此相同.
3.4 声明周期
每个组件在使用时都会触发声明周期函数
生命周期函数需要从svelte中导入并调用
<script>
import { onMount } from 'svelte';
</script>
常用的生命周期函数:
-
onMount
: 组件挂起时调用 -
onDestroy
: 组件销毁后调用 -
beforeUpdate
: 组件数据更新前调用 -
afterUpdate
: 组件数据更新后调用
与Vue相同, 组件内部完成一次数据更新视图的操作之后, 就会触发一次tick函数
tick函数返回一个Promise对象
<script>
import { tick } from 'svelte';
await tick();
todo1();
todo2();
</script>
四. 组件通信
4.1 父组件向子组件传参
父组件在引用子组件属性中传入参数, 子组件用export
关键字定义接收参数的变量
父组件:
<script>
import Nested from './Nested.svelte';
</script>
<Nested answer={42}/>
子组件:
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
再次强调, export
区别于JS ES6在svelte中并不是导出的意思
我们也可以子组件接收数据的变量定义默认值
export let answer = 2;
当需要传入的参数过多时, 可以对象解构的方式自动传入
父组件
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev'
};
</script>
<Info {...pkg}/>
子组件也可以使用svelte内置变量$$props
直接从父组件传入子组件的参数取值,而不用依次定义变量
子组件
<p>
The <code>{$$props.name}</code> package is {$$props.speed} fast.
Download version {$$props.version} from <a href="https://www.npmjs.com/package/{$$props.name}">npm</a>
and <a href={$$props.website}>learn more here</a>
</p>
4.2 子组件向父组件传参
需要使用DOM中的事件分发机制
子组件
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
此时附件就可以接收到一个message事件
父组件
<Child on:message={handleMessage}/>
如果是多层嵌套组件的数据传递, 中间组件可以去除处理方法, 当然也可以重写createEventDispatcher事件;
<Child on:message />
同样的, 如果父组件想监听子组件的事件, 也可以借用该机制
子组件分发点击事件
<button on:click>
Click me
</button>
父组件监听子组件的点击事件
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
alert('Button Clicked');
}
</script>
<CustomButton on:click={handleClick}/>
4.3 表单数据
使用 bind:value={var}
来对表单数据进行双向绑定, 虽然说在DOM中一切都是String类型, 但var定义的类型并不局限于String
如果使用的变量名也为value
, 则可以采用简写
<input bind:value></input>
部分表单组件并不是都跟value属性进行双向绑定, 这点跟Vue中的v-model
有点区别
eg:
<input type=radio bind:group={scoops} name="scoops" value={1}>
<input type=checkbox bind:checked={yes}>
另外, 如果子父组件通信中刚好是用value属性进行属性传递也可以使用 bind:value
进行数据的双向绑定,
但这种方式官方并不推荐, 因为这与 单一数据来源 这一设计理念相违背,数据的追踪也不是很好处理
4.4 状态管理
4.4.1 writable
在store.js
文件中定义一个状态管理器
import { writable } from 'svelte/store';
export const count = writable(0);
注意, 一定要将状态管理器导出
调用状态管理器的update
方法修改状态, 调用subscribe
方法监听状态变化
// 1. 状态修改
count.update(n => n + 1);
// 也可以调用set方法直接赋值
count.set(0);
// 2. 状态变化监听
count.subscribe(value => {
console.log(value);
});
注意, 上面的count不能直接作用于视图, 需要用一个额外的变量托管其值
<script>
import { count } from './stores.js';
let countValue;
count.subscribe(value => {
countValue = value;
});
</script>
<h1>The count is {countValue}</h1>
当前, 你也可以在页面销毁,取消对状态管理的监听
const unsubscribe = count.subscribe(value => {
countValue = value;
});
onDestroy(unsubscribe);
当页面中状态管理过多时, svelte提供了$
语法糖, 使其可以直接获取当前状态值(即: 自动监听 auto-subscribe)
import { count } from './stores.js';
<h1>The count is { $count }</h1>
任何以$
为前缀的变量都会认为状态变量, 因此svelte框架会试图阻止你使用$
前缀定义自己所属变量.
处于自动监听的状态对象, 可以直接对其赋值,即可实现状态与视图的双向绑定
4.4.2 readable
并不是所有的状态都是需要在组件中修改的,当你想定义一个状态并且只想引用时或者其变量只能状态内部发生变化时, 我们可以使用readable
函数定义状态变量
在store.js
中:
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
其中, 第一个参数初始化状态值,可以未null
或者undefined
, 第二个参数定义start: Function
函数, 此函数在初次引用状态时调用, 调用时传入set方法用来修改变量值, 返回一个stop:Function
函数
在取消状态监听时, 会调用stop: Function
函数
import { onDestroy } from 'svelte';
import { count } from './stores.js';
let countValue;
const unsubscribe = count.subscribe(value => {
countValue = value;
});
onDestroy(unsubscribe);
在上述代码中,每执行一次subscribe
方法, 在readable
对象内部就会执行一次subscribes.add
方法, 每次执行一次unsubscribe
方法, 就是执行一次subscribes.delete
方法.
subscribes
是一个Map对象, 当执行unsubscribe
方法时subscribes
的长度为空, 就会执行stop方法, 因此如果一个组件多次实例并销毁而没有调用subscribe返回的unsubscribe取消监听的话, 有可能会造成内存溢出, 因此当调用subscribe时需要记得在合适的时候取消监听
4.4.3 derived
如果定义一个状态需要引入另外一个状态的值, 则需要用derived
函数定义状态, 如:
import { readable, derived } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
}
})
export const elapsed = derived(
time,
$time => Math.round(($time - start) / 1000)
);
五. 模板渲染
5.1 if语句
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}
一般常用, {# if condition}
/ {: else if condition}
/ {: else}
/ {/ if}
其中,
-
#
表示控制语句开始符 -
/
表示控制语句结束符 -
:
表示控制语句继续表达
5.2 each语句 (循环渲染语句)
使用 {# each item as list, index}
来表达循环渲染, 其中list必须为可迭代对象
{#each cats as cat, i}
<li data-id={cat.id}>
{cat.name}
</li>
{/each}
可迭代对象可使用解构方式表达
{#each cats as {id, name}, i}
<li data-id={cat.id}>
{cat.name}
</li>
{/each}
当可迭代对象的长度发生更改时, 如果渲染的是组件, 可能会造成视图渲染的错误/错位, 这时就需要绑定渲染每个item的唯一值, 用(item.id)
表示
<script>
import Thing from './Thing.svelte';
let things = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'carrot' },
{ id: 4, name: 'doughnut' },
{ id: 5, name: 'egg' },
];
</script>
{#each things as thing (thing.id) }
<Thing name={thing.name}/>
{/each}
5.3 await语句
当渲染的数据不是能实时获取时, 可以用await
语句等待数据返回后再进行渲染
<script>
async function getRandomNumber() {
const res = await fetch(`/tutorial/random-number`);
const text = await res.text();
if (res.ok) {
return text;
} else {
throw new Error(text);
}
}
let promise = getRandomNumber();
</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
接收Promise处理成功之后的返回数据变量
catch
接收Promise处理失败之后返回错误对象
六. 内置组件
svelte提供一些内置组件, 默认全局注册,无需额外导入使用
6.1 self
正常情况下, 组件本身无法引用自身组件
例如, 在Folder.svelte组件中
{#if file.files}
<Folder {...file}/>
{:else}
<File {...file}/>
{/if}
上述代码是无法实现的, 因为Folder组件本身无法使用Folder组件, 到可以使用svelte的内置self改造实现,
{#if file.files}
<svelte:self {...file}/>
{:else}
<File {...file}/>
{/if}
6.2 component
动态组件, 类似于Vue中的is=component
例如,不同的条件下渲染不同组件,常规方式下
<script>
const options = [
{ color: 'red', component: RedThing },
{ color: 'green', component: GreenThing },
{ color: 'blue', component: BlueThing },
];
let selected = options[0];
</script>
{#if selected.color === 'red'}
<RedThing />
{:else if selected.color === 'green'}
<GreenThing />
{:else if selected.color === 'blue'}
<BlueThing />
{/if}
使用动态组件可以进行简化
<svelte:component this={selected.component}/>
其中html标签也可以视为一个组件
网友评论