一. 簡介
svelte類似于Vue,是一門純前端框架.區(qū)別于Vue/React,用svelte的話說就是頁面視圖的更新并不是采用類似虛擬DOM的方式.另外傳統(tǒng)的前端框架一般直接返回js文件由瀏覽器編譯成頁面視圖,而svelte編譯視圖的過程則在構(gòu)建編譯時完成,大大減輕了瀏覽器的負擔.
原文: Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
本文主要是對svelte官網(wǎng)中的教程進行翻譯,以便快速了解svelte的一些基礎知識
初始化項目
npm create svelte@latest myapp
cd myapp
npm install
npm run dev
建議使用Vite/Vite-plugin-svelte構(gòu)建工具, 具體可了解(https://sveltesociety.dev/tools)
SvelteKit是用來專門構(gòu)建svelte應用的腳手架(項目構(gòu)建工具), 但目前正在開發(fā)中, 并不建議使用
與Vue相類似,svelte的組件文件是名為.svelte結(jié)尾的文件
注意的是,組件的導入并不需要額外注冊即可使用, 類似于Vue3的setup語法糖
二. 語法
2.1 頁面賦值
定義一個變量, 在視圖中用 {} 進行賦值
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
2.2 動態(tài)屬性
與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類似, 組件樣式直接在組件文件內(nèi)部進行定義
<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內(nèi)容, 如果需要, 則需要使用 @html 修飾符
<p>{@html string}</p>
也可以在html元素中設置contenteditable="true", bind:innerHTML對HTML文本賦值
<div
contenteditable="true"
bind:innerHTML={html}
></div>
2.5 盒子元素屬性
每一元素都有clientWidth, clientHeight, offsetWidth, offsetHeight屬性,
每個屬性都是只讀的, 就算綁定的值發(fā)生變化, 對應的視圖也不會發(fā)生變化
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 事件監(jiān)聽
在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>
此時計算屬性對應的變量也具有響應式渲染視圖的功能,但是該方法不止用定義響應數(shù)據(jù)的功能,也可實現(xiàn)類似watch屬性.
eg:
<script>
let count = 0;
$: {
console.log('the count is ' + count);
alert('I SAID THE COUNT IS ' + count);
}
</script>
此時當count變量值發(fā)生變化時, 就是觸發(fā) $: 定義好方法執(zhí)行, 當然也可以通過if判斷來進行條件執(zhí)行
$: if (count >= 10) {
alert('count is dangerously high!');
count = 9;
}
注意: 響應式變量的更新只能通過給變量賦值來觸發(fā)更新。也就是說
let obj = { a: 1, b: 2, c: 3, d: 4 };
!func() {
obj.a = 2;
}()
<h2>obj.a是 {obj.a}</h2>
此時視圖中顯示的值還是1,即只修改對象內(nèi)部的值并不會觸發(fā)更改, 而且
let obj = { a: 1, b: 2, c: 3, d: 4 };
!func() {
obj.a = 2;
}()
obj = obj
這種賦值方式也是不行的
3.3 事件修飾符
如果只想監(jiān)聽一次DOM的點擊事件, 可以使用|修飾符 on:click|once={function}, 其他修飾符與此相同.
3.4 聲明周期
每個組件在使用時都會觸發(fā)聲明周期函數(shù)
生命周期函數(shù)需要從svelte中導入并調(diào)用
<script>
import { onMount } from 'svelte';
</script>
常用的生命周期函數(shù):
-
onMount: 組件掛起時調(diào)用 -
onDestroy: 組件銷毀后調(diào)用 -
beforeUpdate: 組件數(shù)據(jù)更新前調(diào)用 -
afterUpdate: 組件數(shù)據(jù)更新后調(diào)用
與Vue相同, 組件內(nèi)部完成一次數(shù)據(jù)更新視圖的操作之后, 就會觸發(fā)一次tick函數(shù)
tick函數(shù)返回一個Promise對象
<script>
import { tick } from 'svelte';
await tick();
todo1();
todo2();
</script>
四. 組件通信
4.1 父組件向子組件傳參
父組件在引用子組件屬性中傳入?yún)?shù), 子組件用export關鍵字定義接收參數(shù)的變量
父組件:
<script>
import Nested from './Nested.svelte';
</script>
<Nested answer={42}/>
子組件:
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
再次強調(diào), export區(qū)別于JS ES6在svelte中并不是導出的意思
我們也可以子組件接收數(shù)據(jù)的變量定義默認值
export let answer = 2;
當需要傳入的參數(shù)過多時, 可以對象解構(gòu)的方式自動傳入
父組件
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev'
};
</script>
<Info {...pkg}/>
子組件也可以使用svelte內(nèi)置變量$$props直接從父組件傳入子組件的參數(shù)取值,而不用依次定義變量
子組件
<p>
The <code>{$$props.name}</code> package is {$$props.speed} fast.
Download version {$$props.version} from <a >npm</a>
and <a href={$$props.website}>learn more here</a>
</p>
4.2 子組件向父組件傳參
需要使用DOM中的事件分發(fā)機制
子組件
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
此時附件就可以接收到一個message事件
父組件
<Child on:message={handleMessage}/>
如果是多層嵌套組件的數(shù)據(jù)傳遞, 中間組件可以去除處理方法, 當然也可以重寫createEventDispatcher事件;
<Child on:message />
同樣的, 如果父組件想監(jiān)聽子組件的事件, 也可以借用該機制
子組件分發(fā)點擊事件
<button on:click>
Click me
</button>
父組件監(jiān)聽子組件的點擊事件
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
alert('Button Clicked');
}
</script>
<CustomButton on:click={handleClick}/>
4.3 表單數(shù)據(jù)
使用 bind:value={var} 來對表單數(shù)據(jù)進行雙向綁定, 雖然說在DOM中一切都是String類型, 但var定義的類型并不局限于String
如果使用的變量名也為value, 則可以采用簡寫
<input bind:value></input>
部分表單組件并不是都跟value屬性進行雙向綁定, 這點跟Vue中的v-model有點區(qū)別
eg:
<input type=radio bind:group={scoops} name="scoops" value={1}>
<input type=checkbox bind:checked={yes}>
另外, 如果子父組件通信中剛好是用value屬性進行屬性傳遞也可以使用 bind:value 進行數(shù)據(jù)的雙向綁定,
但這種方式官方并不推薦, 因為這與 單一數(shù)據(jù)來源 這一設計理念相違背,數(shù)據(jù)的追蹤也不是很好處理
4.4 狀態(tài)管理
4.4.1 writable
在store.js文件中定義一個狀態(tài)管理器
import { writable } from 'svelte/store';
export const count = writable(0);
注意, 一定要將狀態(tài)管理器導出
調(diào)用狀態(tài)管理器的update方法修改狀態(tài), 調(diào)用subscribe方法監(jiān)聽狀態(tài)變化
// 1. 狀態(tài)修改
count.update(n => n + 1);
// 也可以調(diào)用set方法直接賦值
count.set(0);
// 2. 狀態(tài)變化監(jiān)聽
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>
當前, 你也可以在頁面銷毀,取消對狀態(tài)管理的監(jiān)聽
const unsubscribe = count.subscribe(value => {
countValue = value;
});
onDestroy(unsubscribe);
當頁面中狀態(tài)管理過多時, svelte提供了$語法糖, 使其可以直接獲取當前狀態(tài)值(即: 自動監(jiān)聽 auto-subscribe)
import { count } from './stores.js';
<h1>The count is { $count }</h1>
任何以$為前綴的變量都會認為狀態(tài)變量, 因此svelte框架會試圖阻止你使用$前綴定義自己所屬變量.
處于自動監(jiān)聽的狀態(tài)對象, 可以直接對其賦值,即可實現(xiàn)狀態(tài)與視圖的雙向綁定
4.4.2 readable
并不是所有的狀態(tài)都是需要在組件中修改的,當你想定義一個狀態(tài)并且只想引用時或者其變量只能狀態(tài)內(nèi)部發(fā)生變化時, 我們可以使用readable函數(shù)定義狀態(tài)變量
在store.js中:
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
其中, 第一個參數(shù)初始化狀態(tài)值,可以未null或者undefined, 第二個參數(shù)定義start: Function函數(shù), 此函數(shù)在初次引用狀態(tài)時調(diào)用, 調(diào)用時傳入set方法用來修改變量值, 返回一個stop:Function函數(shù)
在取消狀態(tài)監(jiān)聽時, 會調(diào)用stop: Function函數(shù)
import { onDestroy } from 'svelte';
import { count } from './stores.js';
let countValue;
const unsubscribe = count.subscribe(value => {
countValue = value;
});
onDestroy(unsubscribe);
在上述代碼中,每執(zhí)行一次subscribe方法, 在readable對象內(nèi)部就會執(zhí)行一次subscribes.add方法, 每次執(zhí)行一次unsubscribe方法, 就是執(zhí)行一次subscribes.delete方法.
subscribes是一個Map對象, 當執(zhí)行unsubscribe方法時subscribes的長度為空, 就會執(zhí)行stop方法, 因此如果一個組件多次實例并銷毀而沒有調(diào)用subscribe返回的unsubscribe取消監(jiān)聽的話, 有可能會造成內(nèi)存溢出, 因此當調(diào)用subscribe時需要記得在合適的時候取消監(jiān)聽
4.4.3 derived
如果定義一個狀態(tài)需要引入另外一個狀態(tài)的值, 則需要用derived函數(shù)定義狀態(tài), 如:
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}
其中,
-
#表示控制語句開始符 -
/表示控制語句結(jié)束符 -
:表示控制語句繼續(xù)表達
5.2 each語句 (循環(huán)渲染語句)
使用 {# each item as list, index} 來表達循環(huán)渲染, 其中l(wèi)ist必須為可迭代對象
{#each cats as cat, i}
<li data-id={cat.id}>
{cat.name}
</li>
{/each}
可迭代對象可使用解構(gòu)方式表達
{#each cats as {id, name}, i}
<li data-id={cat.id}>
{cat.name}
</li>
{/each}
當可迭代對象的長度發(fā)生更改時, 如果渲染的是組件, 可能會造成視圖渲染的錯誤/錯位, 這時就需要綁定渲染每個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語句
當渲染的數(shù)據(jù)不是能實時獲取時, 可以用await語句等待數(shù)據(jù)返回后再進行渲染
<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處理成功之后的返回數(shù)據(jù)變量
catch 接收Promise處理失敗之后返回錯誤對象
六. 內(nèi)置組件
svelte提供一些內(nèi)置組件, 默認全局注冊,無需額外導入使用
6.1 self
正常情況下, 組件本身無法引用自身組件
例如, 在Folder.svelte組件中
{#if file.files}
<Folder {...file}/>
{:else}
<File {...file}/>
{/if}
上述代碼是無法實現(xiàn)的, 因為Folder組件本身無法使用Folder組件, 到可以使用svelte的內(nèi)置self改造實現(xiàn),
{#if file.files}
<svelte:self {...file}/>
{:else}
<File {...file}/>
{/if}
6.2 component
動態(tài)組件, 類似于Vue中的is=component
例如,不同的條件下渲染不同組件,常規(guī)方式下
<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}
使用動態(tài)組件可以進行簡化
<svelte:component this={selected.component}/>
其中html標簽也可以視為一個組件