Svelte 前端框架的核心概念
Svelte 核心概念详解:告别虚拟 DOM,拥抱编译器驱动的响应式
当你首次接触 Svelte 时,最震撼的一点或许是:它并不是一个传统意义上的运行时框架。Svelte 在构建阶段将你的组件编译为高效的原生 JavaScript,直接操作 DOM,无需引入庞大的运行时库。这种设计带来了更小的打包体积和极致性能,也造就了它简洁、直观的语法。本文将带你掌握 Svelte 最核心的概念,从零开始理解它的魅力。
1. 组件:.svelte 文件的三位一体
在 Svelte 中,每个 .svelte 文件就是一个组件。它同时容纳了三部分内容:
<script>– 组件逻辑(JavaScript)<style>– 组件样式(CSS,默认作用域隔离)- 模板代码 – 组件的 HTML 结构
这三者共存于一个文件,让组件的封装性达到极致。
<script>
let name = 'Svelte';
</script>
<style>
h1 {
color: #ff3e00;
}
</style>
<h1>Hello {name}!</h1>
注意:
- 花括号
{}用于在模板中嵌入 JavaScript 表达式。 <style>内的样式默认只作用于当前组件,不会泄漏到外部。
2. 响应式声明:$: 标记的魔法
Svelte 的响应式基于赋值触发。当你对组件内的变量重新赋值时,Svelte 会自动更新 DOM。更强大的地方在于响应式声明:使用 $: 标记的语句会在其依赖的值变化时重新运行。
<script>
let count = 0;
$: double = count * 2; // 依赖 count,count 变化时重新计算
$: {
console.log(`count 是 ${count}`);
}
$: if (count > 10) {
alert('计数大于 10!');
}
function increment() {
count += 1; // 赋值触发响应式
}
</script>
<button on:click={increment}>
点击了 {count} 次,双倍是 {double}
</button>
$: 很像 Vue 中的 computed 与 watch 的结合体,但语法更加灵活,可以直接书写任意语句。
3. 属性(Props):父传子的数据流
组件通过 export 关键字声明对外接收的属性(props)。这看起来和模块导出相反,但在 Svelte 中它代表“这个变量会从父组件传入”。
Child.svelte
<script>
export let greeting;
export let name = '陌生人'; // 可以设置默认值
</script>
<p>{greeting}, {name}!</p>
Parent.svelte
<script>
import Child from './Child.svelte';
let message = '你好';
</script>
<Child greeting={message} name="世界" />
属性是单向数据流,父组件更新 message 时会自动重新渲染子组件。
4. 事件处理:on: 指令
使用 on:事件名 监听 DOM 事件,支持内联处理或调用方法。
<script>
function handleClick(e) {
alert('按钮被点击');
}
</script>
<button on:click={handleClick}>点击我</button>
支持事件修饰符,简化常见操作:
<button on:click|preventDefault={handleClick}>阻止默认行为</button>
<button on:click|once={handleClick}>只触发一次</button>
组件也可以转发事件(事件冒泡),但初学者可稍后再了解。
5. 双向绑定:bind: 指令
对于表单元素,可以用 bind:value 实现数据和输入的双向同步。
<script>
let username = '';
let accepted = false;
</script>
<input type="text" bind:value={username} placeholder="输入用户名" />
<input type="checkbox" bind:checked={accepted} />
<p>用户名:{username}</p>
<p>{accepted ? '已同意' : '未同意'}</p>
bind: 还可以绑定组件属性、尺寸属性(如 clientWidth)等,但需谨慎使用,避免数据流混乱。
6. 条件渲染与循环渲染
条件渲染 使用 {#if} 块,支持 {:else if} 和 {:else}:
<script>
let loggedIn = false;
</script>
{#if loggedIn}
<p>欢迎回来!</p>
{:else}
<p>请登录。</p>
{/if}
循环渲染 使用 {#each} 块,需要唯一的 key 来提高更新性能:
<script>
let todos = [
{ id: 1, text: '学习 Svelte' },
{ id: 2, text: '写项目' }
];
</script>
<ul>
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{/each}
</ul>
如果数组为空,可以添加 {:else} 显示占位内容。
7. 生命周期:组件的关键时刻
Svelte 提供了几个生命周期函数来处理组件的挂载、更新和销毁:
onMount– 组件首次渲染到 DOM 后调用。常用作数据请求。onDestroy– 组件销毁前调用。用于清理定时器等。beforeUpdate/afterUpdate– 在数据变化,DOM 更新前后触发。
<script>
import { onMount, onDestroy } from 'svelte';
let data;
onMount(async () => {
const res = await fetch('/api/data');
data = await res.json();
});
onDestroy(() => {
console.log('组件被销毁了');
});
</script>
{#if data}
<div>{data.name}</div>
{/if}
8. 状态管理:可写 Store(writable)
当多个组件需要共享状态时,Svelte 提供了 Store 机制。最简单的 Store 是 writable:
store.js
import { writable } from 'svelte/store';
export const count = writable(0);
在组件中使用 Store 需要加上 $ 前缀,这是 Svelte 的自动订阅语法糖,自动处理订阅和取消订阅:
<script>
import { count } from './store.js';
function increment() {
$count += 1; // $count 自动订阅,可直接读写
}
</script>
<h1>计数:{$count}</h1>
<button on:click={increment}>+1</button>
Store 也可以派生衍生值 (derived),但核心思想就是集中管理状态,组件通过 $store 自动响应。
9. 逻辑块与特殊元素
- 异步块
{#await}:优雅处理 Promise 的等待、完成和拒绝状态。 <svelte:head>:向文档<head>插入内容,如标题。<svelte:window>:监听 window 事件(如resize),且可绑定属性。<svelte:body>:监听 document.body 事件。
<svelte:head>
<title>动态标题</title>
</svelte:head>
<svelte:window on:keydown={handleKey} bind:scrollY={y} />
这些特殊元素让组件与整个文档的交互变得非常简单。
10. 编译在,简约在——Svelte 的理念
理解以上概念后,你会明白 Svelte 的核心设计哲学:
- 编译时魔法:响应式、过渡动画、样式作用域等均在构建阶段完成,最终生成无框架依赖的原生代码。
- 代码即意图:减少样板代码,结构清晰。例如
$:,bind:,{#each}等都有极强表现力。 - 渐进式学习:组件、响应式、Store 等概念环环相扣,上手后可快速构建复杂应用。
从今天起,忘记“虚拟 DOM diff”,用 Svelte 的方式思考。当你写出 let count = 0; count += 1; 时,Svelte 已经帮你完成了一切更新。这就是编译器框架带来的自由。
下一步
建议立即在 Svelte REPL 中尝试这些概念,然后动手写一个计数器、待办列表或简单的博客应用。当你发现只需极少的代码就能完成同样功能时,便真正体会到了 Svelte 的强大。