响应式状态 $state
本教程共 50 篇 · 第 5 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
5. 响应式状态 $state
本节目标:掌握 $state 的基本用法、深层响应性原理、数组和对象更新方式。学完你能用 $state 创建会自动更新 UI 的状态变量。
$state 是什么
$state 是 Svelte 5 中创建响应式状态的 Rune(符文)。用 $state 声明的变量,一旦值发生变化,依赖它的 UI 会自动更新。
先看一个最简单的例子:
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
点击次数:{count}
</button>
每次点击按钮,count 加 1,按钮文字自动更新。不需要手动调用任何刷新方法。
和某些框架不同,Svelte 的状态就是普通变量——count 是一个数字,不是对象也不是函数。你像操作普通变量一样读写它就行。
$state vs 普通 let
你可能会问:Svelte 4 里直接用 let 声明变量就能响应式更新,为什么 Svelte 5 要多写一个 $state?
区别在于显式 vs 隐式:
| 对比项 | Svelte 4 let | Svelte 5 $state |
|---|---|---|
| 响应式 | 隐式(编译器自动判断) | 显式(你明确声明) |
| 可读性 | 不清楚哪些是响应式的 | 一眼看出响应式状态 |
| 性能 | 编译器分析范围大 | 编译更精确,运行更快 |
<!-- Svelte 4 写法(隐式响应式) -->
<script>
let count = 0;
</script>
<button on:click={() => count++}>{count}</button>
<!-- Svelte 5 写法(显式响应式) -->
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>{count}</button>
NoteSvelte 4 的
let响应式是”猜”的——编译器尝试分析哪些变量被重新赋值了。Svelte 5 改用$state显式声明,编译器不需要猜了,性能和可维护性都更好。
深层响应性
$state 用于数组或普通对象时,会创建一个深层响应代理(Deep Reactive Proxy)。这意味着你修改嵌套属性时,UI 也会自动更新。
<script>
let todos = $state([
{ done: false, text: '学习 Svelte' },
{ done: false, text: '写一个项目' }
]);
function toggleFirst() {
// 直接修改嵌套属性,UI 会自动更新
todos[0].done = !todos[0].done;
}
</script>
<button onclick={toggleFirst}>切换第一个任务</button>
<ul>
{#each todos as todo}
<li>{todo.done ? '✓' : '○'} {todo.text}</li>
{/each}
</ul>
深层响应是递归的。Svelte 会一层层代理,直到遇到非数组、非普通对象的值(如数字、字符串、class 实例)才停止。
Tip打个比方,
$state的深层代理就像一张蜘蛛网。你触碰网上任何一根丝(任何层级的属性),整张网都会感知到震动(触发 UI 更新)。
数组更新
因为 $state 创建的是代理,你可以用任何原生数组方法来更新,UI 都会响应:
<script>
let items = $state(['苹果', '香蕉']);
let newItem = $state('');
function add() {
// push 方法触发更新
items.push(newItem);
newItem = '';
}
function removeFirst() {
// shift 方法触发更新
items.shift();
}
function clearAll() {
// 直接赋值新数组也触发更新
items = [];
}
</script>
<input bind:value={newItem} placeholder="输入水果名" />
<button onclick={add}>添加</button>
<button onclick={removeFirst}>删除第一个</button>
<button onclick={clearAll}>清空</button>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
不管是 push、pop、shift、splice 还是直接赋值新数组,都能触发更新。
对象更新
对象的属性修改同样会触发更新:
<script>
let user = $state({
name: '码上学',
age: 25,
address: {
city: '北京'
}
});
function haveBirthday() {
user.age++;
}
function moveCity() {
// 修改嵌套对象属性,也触发更新
user.address.city = '上海';
}
</script>
<p>{user.name},{user.age}岁,住在{user.address.city}</p>
<button onclick={haveBirthday}>过生日</button>
<button onclick={moveCity}>搬家</button>
解构会丢失响应性
有个重要细节:解构响应式对象会丢失响应性。这是 JavaScript 本身的特性,不是 Svelte 的限制。
<script>
let todos = $state([{ done: false, text: '学习' }]);
// 解构后 done 和 text 是普通值,不再是响应式的
let { done, text } = todos[0];
function toggle() {
todos[0].done = !todos[0].done;
// done 变量不会更新!它还是初始值
console.log(done); // false
}
</script>
Note解构就像把水从杯子里倒出来——水还在,但杯子已经不是原来的杯子了。如果你需要保持响应性,直接访问
todos[0].done而不是解构它。
TypeScript 中使用 $state
在 TypeScript 中,$state 会自动推断类型,也可以显式标注:
<script lang="ts">
let count = $state(0); // 自动推断为 number
let name = $state<string>('Svelte'); // 显式标注
let items = $state<string[]>([]); // 字符串数组
let user = $state<{ name: string; age: number }>({ name: '码上学', age: 25 });
</script>
本节回顾
$state(初始值)创建响应式状态,值变化时 UI 自动更新- Svelte 5 用
$state替代 Svelte 4 的隐式let响应式,显式且更高效 - 数组和对象会被递归代理(深层响应),修改任意层级属性都触发更新
push、splice等数组方法、直接属性赋值、整体重新赋值都能触发更新- 解构响应式对象会丢失响应性,应直接访问属性
- TypeScript 中可自动推断类型或显式标注