响应式声明 #
一、响应式概述 #
Svelte 的响应式系统是其核心特性之一。当状态发生变化时,Svelte 会自动检测并更新相关的 DOM。
1.1 响应式原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ Svelte 响应式原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 编译时 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 分析代码,建立依赖关系图 │ │
│ │ 状态 A ──→ 派生 B ──→ DOM 更新 │ │
│ │ └──────→ 派生 C ──→ DOM 更新 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 运行时 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 状态变化时,精确更新依赖项 │ │
│ │ 无需虚拟DOM,直接操作真实DOM │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
二、Svelte 4 响应式语法 #
2.1 基本状态 #
svelte
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
Count: {count}
</button>
2.2 响应式声明 ($:) #
svelte
<script>
let count = 0;
$: doubled = count * 2;
$: squared = count * count;
</script>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<p>Squared: {squared}</p>
2.3 响应式语句块 #
svelte
<script>
let count = 0;
$: {
console.log('count changed to:', count);
document.title = `Count: ${count}`;
}
</script>
2.4 响应式条件 #
svelte
<script>
let count = 0;
$: if (count > 10) {
console.log('count is greater than 10');
}
</script>
三、Svelte 5 Runes #
3.1 $state - 状态声明 #
svelte
<script>
let count = $state(0);
let name = $state('Svelte');
let items = $state([1, 2, 3]);
let user = $state({ name: 'Alice', age: 25 });
</script>
3.2 $state 的深度响应 #
svelte
<script>
let user = $state({
name: 'Alice',
address: {
city: 'Beijing',
street: 'Main St'
}
});
function updateCity() {
user.address.city = 'Shanghai';
}
</script>
<p>{user.name} lives in {user.address.city}</p>
<button onclick={updateCity}>Move to Shanghai</button>
3.3 $state.raw - 浅响应 #
svelte
<script>
let items = $state.raw([1, 2, 3]);
function addItem() {
items = [...items, items.length + 1];
}
</script>
3.4 类中的 $state #
svelte
<script>
class Counter {
count = $state(0);
increment() {
this.count += 1;
}
get doubled() {
return this.count * 2;
}
}
let counter = new Counter();
</script>
<p>Count: {counter.count}</p>
<p>Doubled: {counter.doubled}</p>
<button onclick={() => counter.increment()}>+</button>
四、$derived - 派生状态 #
4.1 基本用法 #
svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let squared = $derived(count * count);
let message = $derived(`Count is ${count}`);
</script>
<p>{message}</p>
<p>Doubled: {doubled}</p>
<p>Squared: {squared}</p>
4.2 复杂派生 #
svelte
<script>
let firstName = $state('John');
let lastName = $state('Doe');
let fullName = $derived(`${firstName} ${lastName}`);
let items = $state([1, 2, 3, 4, 5]);
let sum = $derived(items.reduce((a, b) => a + b, 0));
let average = $derived(sum / items.length);
</script>
<p>Name: {fullName}</p>
<p>Sum: {sum}, Average: {average}</p>
4.3 $derived.by - 复杂计算 #
svelte
<script>
let items = $state([
{ id: 1, name: 'Apple', price: 1.5 },
{ id: 2, name: 'Banana', price: 2.0 },
{ id: 3, name: 'Orange', price: 1.8 }
]);
let statistics = $derived.by(() => {
const total = items.reduce((sum, item) => sum + item.price, 0);
const count = items.length;
const average = total / count;
const max = Math.max(...items.map(i => i.price));
const min = Math.min(...items.map(i => i.price));
return { total, count, average, max, min };
});
</script>
<p>Total: ${statistics.total.toFixed(2)}</p>
<p>Average: ${statistics.average.toFixed(2)}</p>
<p>Range: ${statistics.min} - ${statistics.max}</p>
五、响应式数组操作 #
5.1 数组更新 #
svelte
<script>
let items = $state([1, 2, 3]);
function addItem() {
items = [...items, items.length + 1];
}
function removeItem(index) {
items = items.filter((_, i) => i !== index);
}
function updateItem(index, value) {
items = items.map((item, i) => i === index ? value : item);
}
</script>
<ul>
{#each items as item, index}
<li>
{item}
<button onclick={() => removeItem(index)}>删除</button>
</li>
{/each}
</ul>
<button onclick={addItem}>添加</button>
5.2 数组方法快捷方式 #
svelte
<script>
let items = $state([1, 2, 3, 4, 5]);
let filtered = $derived(items.filter(i => i > 2));
let mapped = $derived(items.map(i => i * 2));
let sorted = $derived([...items].sort((a, b) => b - a));
</script>
<p>Original: {items.join(', ')}</p>
<p>Filtered: {filtered.join(', ')}</p>
<p>Mapped: {mapped.join(', ')}</p>
<p>Sorted: {sorted.join(', ')}</p>
六、响应式对象操作 #
6.1 对象更新 #
svelte
<script>
let user = $state({
name: 'Alice',
age: 25,
address: {
city: 'Beijing'
}
});
function updateName(name) {
user.name = name;
}
function updateAge(age) {
user.age = age;
}
function updateCity(city) {
user.address.city = city;
}
</script>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>City: {user.address.city}</p>
6.2 对象展开更新 #
svelte
<script>
let settings = $state({
theme: 'light',
fontSize: 14,
notifications: true
});
function updateSettings(newSettings) {
settings = { ...settings, ...newSettings };
}
function toggleTheme() {
settings.theme = settings.theme === 'light' ? 'dark' : 'light';
}
</script>
<button onclick={toggleTheme}>
Current theme: {settings.theme}
</button>
七、响应式最佳实践 #
7.1 避免过度派生 #
svelte
<script>
let items = $state([1, 2, 3, 4, 5]);
let sum = $derived(items.reduce((a, b) => a + b, 0));
let average = $derived(sum / items.length);
</script>
7.2 使用 $derived.by 处理复杂逻辑 #
svelte
<script>
let data = $state([]);
let filter = $state('');
let sortKey = $state('name');
let processed = $derived.by(() => {
let result = data.filter(item =>
item.name.includes(filter)
);
result.sort((a, b) => {
if (a[sortKey] < b[sortKey]) return -1;
if (a[sortKey] > b[sortKey]) return 1;
return 0;
});
return result;
});
</script>
7.3 合理拆分状态 #
svelte
<script>
let user = $state({ name: '', email: '' });
let preferences = $state({ theme: 'light', lang: 'en' });
let cart = $state([]);
let cartTotal = $derived(
cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
</script>
八、Svelte 4 vs Svelte 5 对比 #
8.1 语法对比 #
| 功能 | Svelte 4 | Svelte 5 |
|---|---|---|
| 状态 | let count = 0 |
let count = $state(0) |
| 派生 | $: doubled = count * 2 |
let doubled = $derived(count * 2) |
| 副作用 | $: { ... } |
$effect(() => { ... }) |
| Props | export let prop |
let { prop } = $props() |
8.2 迁移示例 #
Svelte 4:
svelte
<script>
export let initialCount = 0;
let count = initialCount;
$: doubled = count * 2;
$: if (count > 10) {
console.log('count exceeded 10');
}
</script>
Svelte 5:
svelte
<script>
let { initialCount = 0 } = $props();
let count = $state(initialCount);
let doubled = $derived(count * 2);
$effect(() => {
if (count > 10) {
console.log('count exceeded 10');
}
});
</script>
九、完整示例 #
9.1 购物车组件 #
svelte
<script>
let { items = [] } = $props();
let cart = $state([]);
let totalItems = $derived(
cart.reduce((sum, item) => sum + item.quantity, 0)
);
let totalPrice = $derived(
cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
let discount = $derived(totalPrice > 100 ? totalPrice * 0.1 : 0);
let finalPrice = $derived(totalPrice - discount);
function addToCart(item) {
const existing = cart.find(i => i.id === item.id);
if (existing) {
existing.quantity += 1;
} else {
cart = [...cart, { ...item, quantity: 1 }];
}
}
function removeFromCart(id) {
cart = cart.filter(item => item.id !== id);
}
function updateQuantity(id, quantity) {
cart = cart.map(item =>
item.id === id ? { ...item, quantity } : item
);
}
</script>
<div class="cart">
<h2>购物车 ({totalItems} 件商品)</h2>
{#if cart.length === 0}
<p class="empty">购物车是空的</p>
{:else}
<ul class="cart-items">
{#each cart as item}
<li>
<span class="name">{item.name}</span>
<span class="price">${item.price}</span>
<input
type="number"
min="1"
bind:value={item.quantity}
/>
<button onclick={() => removeFromCart(item.id)}>删除</button>
</li>
{/each}
</ul>
<div class="summary">
<p>小计: ${totalPrice.toFixed(2)}</p>
{#if discount > 0}
<p class="discount">折扣: -${discount.toFixed(2)}</p>
{/if}
<p class="total">总计: ${finalPrice.toFixed(2)}</p>
</div>
{/if}
</div>
<style>
.cart {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
.empty {
color: #999;
text-align: center;
padding: 2rem;
}
.cart-items li {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.summary {
margin-top: 1rem;
text-align: right;
}
.discount {
color: green;
}
.total {
font-size: 1.2rem;
font-weight: bold;
}
</style>
十、总结 #
| 概念 | 说明 |
|---|---|
$state |
声明响应式状态 |
$derived |
声明派生状态 |
$derived.by |
复杂派生计算 |
$state.raw |
浅响应状态 |
响应式原则:
- 状态变化自动触发更新
- 派生状态自动追踪依赖
- 避免在派生中产生副作用
- 合理拆分状态,提高可维护性
最后更新:2026-03-28