Svelte Legacy 组件 props 详解:export let 的声明、默认值与组件 API 导出
本文基于 Svelte 官方文档 documentation/docs/99-legacy/03-legacy-export-let.md 展开,完整讲解 legacy 模式(非 runes 模式)下 export let 声明组件 props 的全部用法:默认值语义、必需 prop 的缺失警告与抑制方式、const/class/function 声明成为组件 API 而非 prop 的区别,以及用 export { x as y } 重命名 prop(如处理保留字)的技巧。读完后,你可以准确判断一段 legacy 组件脚本中哪些标识符是 prop、哪些是 API 导出,并能对照 Svelte 编译器源码理解这些规则的判定依据。
1. 背景:runes 与 legacy 两种 props 声明方式
在 runes 模式(Svelte 5 的默认推荐方式)下,组件 props 通过 $props() rune 声明,父组件传入的数据以解构形式接收:
<script>
let { foo, bar = 'default value' } = $props();
</script>
而在 legacy 模式下,props 通过 export 关键字标记,并且可以带默认值:
<script>
export let foo;
export let bar = 'default value';
// 作为 props 传入的值
// 在组件实例代码中立即可用
console.log({ foo });
</script>
官方文档指出两者的一个关键行为差异(原文档中的 NOTE):
与 runes 模式不同,如果父组件把一个 prop 从“有值”改为
undefined,legacy 模式下的该 prop 不会回退到初始默认值。也就是说bar = 'default value'中的'default value'只在组件创建时该 prop 为undefined的情况下生效;之后父组件显式传undefined时,组件内拿到的就是undefined,而不是'default value'。
这一点在混合 runes/legacy 代码库、或从 Svelte 4 迁移代码时尤其需要注意:不能假设两种模式的 props 回退行为一致。Svelte 5 迁移指南(documentation/docs/07-misc/07-v5-migration-guide.md)中 “export let → props` 统一到了 rune 语义中。
2. 源码级机制:编译器如何判定 export let 是 prop
Svelte 编译器在 analyze 阶段(phases/2-analyze)对非 runes 组件的 <script> 体逐条扫描 ExportNamedDeclaration 节点,判定规则可以从 packages/svelte/src/compiler/phases/2-analyze/index.js 中直接读到(源码注释原话是 “every exported let or var declaration becomes a prop, everything else becomes an export”):
export let/export var声明:绑定(binding)的kind被置为bindable_prop,即该标识符是一个 prop,并且是可双向绑定的(父组件可以用bind:foo={x});export const、export function、export class声明:不成为 prop,而是被推入analysis.exports,作为组件的具名导出(组件 API);export { a as b }这类重导出:如果local对应的是let/var绑定,同样标记为bindable_prop,且当导出名与本地名不同时,记录binding.prop_alias = 导出名;否则记为普通导出。
另外,任何 export let 的存在都会把组件标记为 analysis.needs_props = true。
对应的错误检查逻辑在 packages/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js:runes 模式下禁止 export let,抛出 legacy_export_invalid 错误(“Cannot use export let in runes mode — use $props() instead”),错误定义见 packages/svelte/src/compiler/errors.js,对应的测试样例在 packages/svelte/tests/compiler-errors/samples/runes-export-let/_config.js。
由此可以推断出两条实操结论:
- legacy 组件中只有
let/var形式的导出是 prop,const导出永远是只读 API; - prop 天然是双向可绑定的(
bindable_prop),这是 legacy 模式相对$props()的一个便利性——在 runes 模式里需要$bindable()显式声明。
3. 默认值与“必需 prop”的缺失警告
3.1 默认值语义
如前所述,默认值仅在组件创建时该 prop 为 undefined 时使用。注意这与 JS 的解构默认值不同:export let bar = 'default value' 不是对 bar 做 bar ?? 'default value',而是初始赋值。
3.2 无默认值的 prop 视为 required
没有默认值的 prop 被视为必需(required)。如果父组件未提供值,Svelte 会在开发模式下打印警告。要抑制这个警告,可以把默认值显式设为 undefined:
export let foo = undefined;
这样编译器就知道“这个 prop 允许为空”,不再把它当作漏传。
3.3 相关的编译期警告:export_let_unused
还有一个容易混淆的编译警告值得了解:export_let_unused,其消息定义为(见 packages/svelte/messages/compile-warnings/script.md):
Component has unused export property '%name%'. If it is for external reference only, please consider using
export const %name%
它由 packages/svelte/src/compiler/phases/2-analyze/index.js 中的检查触发:遍历实例作用域,凡是 kind 为 prop 或 bindable_prop 且除声明与导出标记外没有任何引用的绑定,就会发出该警告。换句话说——你 export let 了却在模板和脚本里都没用它——要么它是多余的,要么它本应写成 export const。这对代码审查很实用:看到这条警告时,先确认该 prop 是否真的被外部 bind: 使用,若只是给外部引用则改用 const。
4. 组件导出:const / class / function 不是 prop
官方文档的 “Component exports” 一节强调:导出的 const、class 或 function 声明不被视为 prop,而是组件 API 的一部分。示例来自文档原文:
Greeter.svelte:
<!--- file: Greeter.svelte--->
<script>
export function greet(name) {
alert(`hello ${name}!`);
}
</script>
App.svelte:
<!--- file: App.svelte --->
<script>
import Greeter from './Greeter.svelte';
let greeter;
</script>
<Greeter bind:this={greeter} />
<button on:click={() => greeter.greet('world')}>
greet
</button>
通过 bind:this={greeter} 拿到组件实例引用后,就可以调用 greeter.greet('world')。这与第 2 节的源码判定一致:ExportNamedDeclaration 中 FunctionDeclaration/ClassDeclaration/const 分支只会被推入 analysis.exports(见 packages/svelte/src/compiler/phases/2-analyze/index.js),不会触碰 props 机制。
需要注意的前提:bind:this 是 legacy 的实例访问方式;在 runes 模式下访问组件实例 API 有专门规范(参见 documentation/docs/06-runtime/04-imperative-component-api.md 中对 imperative component API 的说明),本文聚焦 legacy 语义。
5. 重命名 props:export 与声明分离
export 关键字也可以脱离声明单独出现。文档给出的典型场景是给保留字命名的 prop 重命名:
<!--- file: App.svelte --->
<script>
/** @type {string} */
let className;
// 创建一个 `class` 属性,
// 尽管它是保留字
export { className as class };
</script>
此时对外暴露的 prop 名是 class(父组件写作 <App class="x" /> 或通过 spread 传入),而组件内部通过本地变量 className 使用它。对应源码逻辑:在 analyze 阶段处理 export { a as b } 时,若 a 是 let/var 绑定则标记为 bindable_prop,且 b !== a 时设置 binding.prop_alias = b(packages/svelte/src/compiler/phases/2-analyze/index.js)。这个 prop_alias 字段正是后续 transform 阶段生成 props 接收代码、以及模板中按原始名称引用时进行映射的依据。
由此可以推断出 legacy 模式的完整心智模型:
| 写法 | 语义 | 可否 bind: |
备注 |
|---|---|---|---|
export let foo; |
prop,required | 是 | 未传值时开发环境警告 |
export let bar = 'x'; |
prop,带默认值 | 是 | 仅创建期为 undefined 时生效 |
export let foo = undefined; |
prop,可选 | 是 | 抑制 required 缺失警告 |
let x; export { x as class }; |
prop,对外名 class |
是 | 处理保留字等场景 |
export const / function / class |
组件 API 导出 | 否 | 通过实例引用访问 |
6. 与 runes 模式的互斥与迁移提示
- 一个组件要么是 runes 模式(
<script>中使用了 rune,或options.runes: true),要么是 legacy 模式,二者对export let的态度截然相反:runes 模式直接编译报错legacy_export_invalid(错误文案与测试样例见 packages/svelte/messages/compile-errors/script.md 及 packages/svelte/tests/compiler-errors/samples/runes-export-let/_config.js);legacy 模式则如本文所述处理。 - 从 legacy 迁移到 runes 时,
export let统一替换为$props()解构,默认值写法不变(bar = 'default value'),而 legacy 中“所有export let天然可绑定”的能力需要用$bindable(bar)显式表达。完整对照见 documentation/docs/07-misc/07-v5-migration-guide.md 的 “export let → $props” 一节。 - 仓库中存在大量使用
export let的 legacy 测试样例(如 packages/svelte/tests/runtime-legacy/samples/ 目录下的各组件),可作为真实用法的语料参照。
7. 小结
- legacy 模式 props = 实例作用域中被导出的
let/var声明(含export { x as y }重命名),可双向绑定,默认值只在创建期为undefined时生效;父组件后续改传undefined不会回退默认值。 export const/function/class是组件 API 而非 prop,经bind:this实例引用访问。- required prop 未传值会在开发环境警告,
export let foo = undefined;可显式标记为可选以消除警告;export_let_unused警告则提示你“导出了却没用”,应考虑改为const。 - runes 模式禁用
export let(legacy_export_invalid编译错误),迁移路径是$props()。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00