首页 / Svelte 5 入门教程 / 响应式状态 $state

Svelte 5 入门教程

响应式状态 $state

本教程共 50 篇 · 第 5 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

SvelteSvelte 5Runes$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 letSvelte 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>
Note

Svelte 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>

不管是 pushpopshiftsplice 还是直接赋值新数组,都能触发更新。

对象更新

对象的属性修改同样会触发更新:

<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 响应式,显式且更高效
  • 数组和对象会被递归代理(深层响应),修改任意层级属性都触发更新
  • pushsplice 等数组方法、直接属性赋值、整体重新赋值都能触发更新
  • 解构响应式对象会丢失响应性,应直接访问属性
  • TypeScript 中可自动推断类型或显式标注