首页
/ Svelte `bind:` 指令详解:双向数据绑定的完整语法、内置绑定类型与源码级实现

Svelte `bind:` 指令详解:双向数据绑定的完整语法、内置绑定类型与源码级实现

2026-09-04 23:25:54作者:羿妍玫Ivan

本篇基于 Svelte 官方模板语法文档 12-bind.md 展开,系统讲解 bind: 指令的通用语法、函数式绑定、各类内置元素绑定(表单、媒体、尺寸、contenteditable、bind:this)以及组件 prop 绑定($bindable)。读完本文,你不仅能掌握所有 bind: 变体的写法与边界条件(数值强制转换、表单重置行为、FileList 限制等),还能理解编译器如何校验绑定、运行时如何生成 $.bind_* 调用,从而在生产代码中正确编写表单与媒体绑定。

基本语法:数据从子流向父

Svelte 中数据通常自上而下流动(父组件 → 子组件)。bind: 指令让数据可以反向流动(子 → 父)。

通用语法为 bind:property={expression},其中 expression 必须是一个 lvalue(可赋值表达式,即变量或对象属性)。当表达式是与属性同名的标识符时,可以省略表达式——以下两种写法等价:

<input bind:value={value} />
<input bind:value />

Svelte 会为该元素创建一个事件监听器来更新被绑定的值。如果元素上已经存在同一事件的监听器,那个监听器会在绑定值更新之前先被触发。

大多数绑定是双向的(two-way):改变绑定的值会反映到元素上,反之亦然。少数绑定是只读的(readonly):改变绑定的值不会对元素产生任何效果(例如尺寸、媒体进度类属性)。

函数绑定:bind:property={get, set}

除了普通表达式,还可以使用 bind:property={get, set} 的形式,其中 getset 都是函数,从而在读写时执行校验或转换:

<input bind:value={
	() => value,
	(v) => value = v.toLowerCase()}
/>

对于只读绑定(例如尺寸绑定),get 必须写成 null

<div
	bind:clientWidth={null, redraw}
	bind:clientHeight={null, redraw}
>...</div>

注意:函数绑定从 Svelte 5.9.0 起可用。

从源码结构看,编译器把 bind:property={get, set} 解析为 JS 的 SequenceExpression。分析阶段在 BindDirective.js 中处理:要求表达式恰好包含两个子表达式,禁止出现 await,并且 bind:group 不支持函数绑定形式(会直接报 bind_group_invalid_expression 错误)。

<input bind:value>

<input> 上的 bind:value 绑定其 value 属性:

<script>
	let message = $state('hello');
</script>

<input bind:value={message} />
<p>{message}</p>

对于数值型输入(type="number"type="range"),值会被强制转换为数字:

<!--- file: App.svelte --->
<script>
	let a = $state(1);
	let b = $state(2);
</script>

<label>
	<input type="number" bind:value={a} min="0" max="10" />
	<input type="range" bind:value={a} min="0" max="10" />
</label>

<label>
	<input type="number" bind:value={b} min="0" max="10" />
	<input type="range" bind:value={b} min="0" max="10" />
</label>

<p>{a} + {b} = {a + b}</p>

输入为空或非法(type="number" 的情况下)时,绑定值为 undefined。这一点在运行时源码中可以直接印证:input.js 中的 bind_value 会监听 input 事件,对 numberlike 输入执行 to_number 转换,随后通过 render_effect 把状态值写回 input.value

自 5.6.0 起,如果 <input> 带有 defaultValue 且位于表单内,表单重置时会恢复到该值而不是空字符串。初始渲染时,只要绑定值不是 null/undefined,绑定值优先于 defaultValue

<script>
	let value = $state('');
</script>

<form>
	<input bind:value defaultValue="not the empty string">
	<input type="reset" value="Reset">
</form>

这个行为的运行时依据在 bind_value 的事件回调中:var value = is_reset ? input.defaultValue : input.value(见 input.js)。表单重置与 defaultValue 的行为有专门测试覆盖,可参考 bindings-form-reset 测试form-default-value 测试

注意:请谨慎使用 reset 按钮,并确保用户不会在尝试提交表单时误点它。

<input bind:checked>

复选框可用 bind:checked 绑定:

<label>
	<input type="checkbox" bind:checked={accepted} />
	Accept terms and conditions
</label>

自 5.6.0 起,带 defaultChecked 属性且位于表单内的 <input> 在表单重置时会恢复到该值而不是 false。初始渲染时,只要绑定值不是 null/undefined,绑定值优先:

<script>
	let checked = $state(true);
</script>

<form>
	<input type="checkbox" bind:checked defaultChecked={true}>
	<input type="reset" value="Reset">
</form>

注意:单选按钮(radio)应使用 bind:group 而不是 bind:checked。这一点在编译器分析阶段就会被强制校验:在 BindDirective.js 中,<input type="radio"> 上使用 bind:checked 会直接抛出编译错误并提示改用 bind:group

<input bind:indeterminate>

复选框可以处于独立于勾选/未勾选的**半选(indeterminate)**状态:

<script>
	let checked = $state(false);
	let indeterminate = $state(true);
</script>

<form>
	<input type="checkbox" bind:checked bind:indeterminate>

	{#if indeterminate}
		waiting...
	{:else if checked}
		checked
	{:else}
		unchecked
	{/if}
</form>

在编译器绑定注册表 bindings.js 中,indeterminate 被标记为 bidirectional: true、仅适用于 input,且监听的事件是 change(而非 input),这解释了为什么半选状态切换与勾选状态更新节奏一致。

<input bind:group>

一组协同工作的输入可以使用 bind:group

<!--- file: App.svelte --->
<script>
	let tortilla = $state('Plain');

	/** @type {string[]} */
	let fillings = $state([]);
</script>

<h1>Customize your burrito</h1>

<!-- grouped radio inputs are mutually exclusive -->
<label><input type="radio" bind:group={tortilla} value="Plain" /> Plain</label>
<label><input type="radio" bind:group={tortilla} value="Whole wheat" /> Whole wheat</label>
<label><input type="radio" bind:group={tortilla} value="Spinach" /> Spinach</label>

<!-- grouped checkbox inputs populate an array -->
<label><input type="checkbox" bind:group={fillings} value="Rice" /> Rice</label>
<label><input type="checkbox" bind:group={fillings} value="Beans" /> Beans</label>
<label><input type="checkbox" bind:group={fillings} value="Cheese" /> Cheese</label>
<label><input type="checkbox" bind:group={fillings} value="Guac (extra)" /> Guac (extra)</label>

<p>Tortilla: {tortilla}</p>
<p>Fillings: {fillings.join(', ') || 'None'}</p>

<style>
	label {
		display: block;
	}
</style>

注意:bind:group 只在输入位于同一个 Svelte 组件内时有效。

源码层面,bind:group 的分组逻辑在 BindDirective.js 中实现:编译器向上遍历 AST 找出引用了绑定标识符的 EachBlock,并按“相同标识符序列”把多个输入划入同一个 binding_group;运行时由 input.js 中的 bind_group 函数把每个 <input> 推入组内数组,单选输入取 input.__value,多选(checkbox)则通过 get_binding_group_value 生成数组。相关测试可见 state-bind-group 测试

<input bind:files>

对于 type="file"<input>,可以用 bind:files 获取所选文件的 FileList。若要编程式更新文件,必须传入 FileList 对象;由于 FileList 目前无法直接构造,需要创建一个新的 DataTransfer 对象并取其 files

<script>
	let files = $state();

	function clear() {
		files = new DataTransfer().files; // null or undefined does not work
	}
</script>

<label for="avatar">Upload a picture:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />
<button onclick={clear}>clear</button>

FileList 对象也不可变。例如要删除列表中的单个文件,需要新建 DataTransfer 并只添加你想保留的文件。

注意:DataTransfer 在某些服务端 JS 运行时中不可用。把绑定 files 的状态保持未初始化,可以防止组件在服务端渲染(SSR)时产生潜在错误。这与编译器注册表中 files 绑定带有 omit_in_ssr: true 标记(见 bindings.js)相一致。

<select bind:value>

<select>value 绑定对应于被选中 <option>value 属性,它可以是任意值(不像常规 DOM 只能是字符串):

<select bind:value={selected}>
	<option value={a}>a</option>
	<option value={b}>b</option>
	<option value={c}>c</option>
</select>

<select multiple> 的行为类似一组复选框:绑定变量是一个数组,每个被选中 <option>value 对应一个条目:

<select multiple bind:value={fillings}>
	<option value="Rice">Rice</option>
	<option value="Beans">Beans</option>
	<option value="Cheese">Cheese</option>
	<option value="Guac (extra)">Guac (extra)</option>
</select>

<option>value 与其文本内容一致时,value 属性可以省略:

<select multiple bind:value={fillings}>
	<option>Rice</option>
	<option>Beans</option>
	<option>Cheese</option>
	<option>Guac (extra)</option>
</select>

可以给 <select> 设置默认值:在应当被初始选中的 <option>multiple 时可以是多个)上加 selected 属性。如果 <select> 位于表单内,表单重置时会恢复到该选中状态。初始渲染时,只要绑定值不是 undefined,绑定值优先:

<select bind:value={selected}>
	<option value={a}>a</option>
	<option value={b} selected>b</option>
	<option value={c}>c</option>
</select>

自 5.57.0 起,带 defaultValue 且位于表单内的 <select> 在表单重置时会恢复到该值而不是空字符串。初始渲染时,只要绑定值不是 null/undefined,绑定值优先:

<form>
	<select bind:value defaultValue="b">
		<option>a</option>
		<option>b</option>
		<option>c</option>
	</select>
	<input type="reset" value="Reset">
</form>

<select> 的绑定由 select.js 中的 bind_select_value 实现,它专门处理“非字符串 value”这类原生 <select> 无法表达的场景。

<audio><video>

<audio> 元素有一组专属绑定——五个双向绑定:

  • currentTime
  • playbackRate
  • paused
  • volume
  • muted

以及一组只读绑定:

  • duration
  • buffered
  • seekable
  • seeking
  • ended
  • readyState
  • played
<audio src={clip} bind:duration bind:currentTime bind:paused></audio>

<video> 元素拥有与 <audio> 完全相同的绑定,另加只读的 videoWidthvideoHeight 两个绑定。

这些属性在 bindings.js 中均有注册:双向项带 bidirectional: true,媒体事件(durationchangeresize 等)由 event 字段声明,统一由编译器生成形如 $.bind_current_time$.bind_paused$.bind_volume 的运行时调用(见 3-transform/client/visitors/BindDirective.js),并全部标记 omit_in_ssr: true,即服务端渲染时不生效。

<img>

<img> 元素有两个只读绑定:

  • naturalWidth
  • naturalHeight

从注册表可见(bindings.js),二者绑定到 load 事件上——即图片加载完成后触发更新,仅适用于 img 元素。

<details bind:open>

<details> 元素支持对 open 属性绑定:

<details bind:open={isOpen}>
	<summary>How do you comfort a JavaScript bug?</summary>
	<p>You console it.</p>
</details>

注册表中 open 对应的事件是 togglebindings.js),且为双向绑定,只适用于 details 元素。

windowdocument

要绑定 windowdocument 的属性,请使用 <svelte:window><svelte:document> 组件(例如 scrollXinnerWidthonlineactiveElementvisibilityState 等均在 bindings.js 中限定为仅适用于这两个特殊元素)。

Contenteditable 绑定

contenteditable 属性的元素支持以下绑定:

  • innerHTML
  • innerText
  • textContent

注意:innerTexttextContent 之间存在细微差别(前者参与可见性/渲染模型,后者直接反映 DOM 文本)。

<div contenteditable="true" bind:innerHTML={html}></div>

这类绑定受严格校验:若元素没有 contenteditable 属性,分析阶段会抛出 attribute_contenteditable_missing 错误;若 contenteditable 是动态表达式,则抛出 attribute_contenteditable_dynamic(见 BindDirective.js)。运行时则由 $.bind_content_editable 统一处理三种属性。

尺寸绑定(Dimensions)

所有可见元素都支持以下只读绑定,底层使用 ResizeObserver 测量:

  • clientWidth
  • clientHeight
  • offsetWidth
  • offsetHeight
  • contentRect
  • contentBoxSize
  • borderBoxSize
  • devicePixelContentBoxSize
<div bind:offsetWidth={width} bind:offsetHeight={height}>
	<Chart {width} {height} />
</div>

注意:display: inline 元素没有宽高(“固有尺寸”元素如 <img><canvas> 除外),无法被 ResizeObserver 观察。需要将其 display 改为 inline-block 等。另外 CSS 变换(transform)不会触发 ResizeObserver 回调。

从源码结构看,尺寸类绑定分两条路径生成:contentRectResizeObserverEntry 字段走 $.bind_resize_observer,而 clientWidth/offsetHeight 等 CSSOM 数值走 $.bind_element_size(见 3-transform/client/visitors/BindDirective.js)。另外编译器还有一条针对性规则:SVG 元素不允许使用 bind:offsetWidth,会提示改用 bind:clientWidthBindDirective.js)。

bind:this:获取 DOM 节点或组件实例引用

<!--- copy: false --->
bind:this={dom_node}

要获取 DOM 节点引用,使用 bind:this。组件挂载之前该值为 undefined——也就是说,你应该在effect 或事件处理器中读取它,而不是在组件初始化期间读取:

<script>
	/** @type {HTMLCanvasElement} */
	let canvas;

	$effect(() => {
		const ctx = canvas.getContext('2d');
		drawStuff(ctx);
	});
</script>

<canvas bind:this={canvas}></canvas>

组件同样支持 bind:this,允许你以编程方式操作组件实例:

<!--- file: App.svelte --->
<ShoppingCart bind:this={cart} />

<button onclick={() => cart.empty()}> Empty shopping cart </button>
<!--- file: ShoppingCart.svelte --->
<script>
	// All instance exports are available on the instance object
	export function empty() {
		// ...
	}
</script>

注意:当结合函数绑定使用时,getter 是必需的,这样才能保证组件或元素销毁时正确的值被置空。

在编译器转换阶段,bind:this 是特殊路径:它走 build_bind_this,且语句被推入 init 阶段(先于渲染),因为它是一向绑定、可能影响渲染 effect;其余绑定则推入 after_update 阶段,确保绑定发生在属性更新之后、与事件/action 保持顺序(见 3-transform/client/visitors/BindDirective.js)。运行时实现位于 this.js

组件的 bind:property:借助 $bindable 的 prop 绑定

bind:property={variable}

组件的 prop 可以用与元素完全相同的语法进行绑定:

<Keypad bind:value={pin} />

虽然 Svelte 的 prop 本身是响应式的,但默认情况下这种响应性只向下流入组件。使用 bind:property 可以让组件内部对该属性的修改回流到父组件。

要声明一个属性可绑定,使用 $bindable rune:

<script>
	let { readonlyProperty, bindableProperty = $bindable() } = $props();
</script>

声明为 bindable 表示该属性可以bind: 绑定,而不是必须bind: 绑定。

可绑定属性可以带回退值(fallback value):

<script>
	let { bindableProperty = $bindable('fallback value') } = $props();
</script>

这个回退值只在属性未被绑定时生效。当属性被绑定且存在回退值时,父组件必须提供非 undefined 的值,否则会抛出运行时错误——这避免了“到底该用哪个值”这类难以推理的场景。

分析阶段同样会校验被绑定标识符的“可写性”:只有 stateraw_statepropbindable_propeachstore_sub 等类型的绑定(或已被更新过的绑定)才能作为绑定目标,否则报 bind_invalid_value(见 BindDirective.js)。

小结:从语法到代码生成的一条链路

综合文档与源码,Svelte 的 bind: 机制可以概括为三层:

  1. 注册表层packages/svelte/src/compiler/phases/bindings.jsbinding_properties 表集中声明每个绑定名的适用元素(valid_elements/invalid_elements)、驱动事件(event)、是否双向(bidirectional)、是否在 SSR 中省略(omit_in_ssr)。文档中所有“只读/双向”“仅某元素可用”的描述都可以在此表中一一对应。
  2. 分析与转换层2-analyze/visitors/BindDirective.js 负责校验(元素与绑定名的匹配、type="file" 检查、contenteditable 要求、bind:group 分组、函数绑定语法);3-transform/client/visitors/BindDirective.js 负责把 bind:* 翻译为 $.bind_value$.bind_group$.bind_select_value 等运行时调用,并按 init/after_update 阶段安排执行顺序。
  3. 运行时层packages/svelte/src/internal/client/dom/elements/bindings/ 目录下的 input.jsselect.jsmedia.jssize.jswindow.jsdocument.js 等文件实现了各类绑定的双向同步逻辑(如 bind_value 中“聚焦中的输入不被重写”“数值输入的 0/00 相等处理”“表单重置时回落到 defaultValue”等细节),并有 runtime-runes 测试集 中的 bindings-form-resetform-default-valuestate-bind-group 等样例持续验证这些行为。

掌握这张“文档 → 编译器 → 运行时”的对应关系后,遇到绑定不生效、SSR 报 DataTransfer 不存在、或表单重置行为不符合预期等问题时,就能快速定位到具体的实现与测试代码进行排查。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384