$props

组件的输入被称为 props(即 properties 的缩写)。向组件传递 props 的方式,与向元素传递属性的方式完全一样:

<!--- file: App.svelte --->
<script>
	import MyComponent from './MyComponent.svelte';
</script>

<MyComponent adjective="cool" />

而在另一侧的 MyComponent.svelte 内部,我们可以通过 $props 符文来接收 props……

<!--- file: MyComponent.svelte --->
<script>
	let props = $props();
</script>

<p>this component is {props.adjective}</p>

……不过更常见的做法是解构你的 props:

<!--- file: MyComponent.svelte --->
<script>
	let { adjective } = $props();
</script>

<p>this component is {adjective}</p>

回退值

解构允许我们声明回退值(fallback value),当父组件没有设置某个 prop(或值为 undefined)时会使用它:

let { adjective = 'happy' } = $props();

[!NOTE] 回退值不会被转换成响应式状态代理(详见更新 props

重命名 props

我们也可以使用解构赋值来重命名 props,当它们是无效标识符或是像 super 这样的 JavaScript 关键字时,就必须这么做:

let { super: trouper = 'lights are gonna find me' } = $props();

剩余 props

最后,我们还可以使用_剩余属性_来获取其余的 props:

let { a, b, c, ...others } = $props();

更新 props

组件内对某个 prop 的引用,会随该 prop 自身的更新而更新——当 countApp.svelte 中发生变化时,它在 Child.svelte 内部也会随之变化。不过,子组件可以临时覆盖该 prop 的值,这在处理尚未保存的临时状态时很有用:

<!--- file: App.svelte --->
<script>
	import Child from './Child.svelte';

	let count = $state(0);
</script>

<button onclick={() => (count += 1)}>
	clicks (parent): {count}
</button>

<Child {count} />
<!--- file: Child.svelte --->
<script>
	let { count } = $props();
</script>

<button onclick={() => (count += 1)}>
	clicks (child): {count}
</button>

尽管你可以临时_重新赋值_ props,但不应_修改_ props,除非它们是可绑定的

如果 prop 是一个普通对象,修改它不会产生任何效果:

<!--- file: App.svelte --->
<script>
	import Child from './Child.svelte';
</script>

<Child object={{ count: 0 }} />
<!--- file: Child.svelte --->
<script>
	let { object } = $props();
</script>

<button onclick={() => {
	// has no effect
	object.count += 1
}}>
	clicks: {object.count}
</button>

然而,如果 prop 是一个响应式状态代理,那么修改_确实_会产生效果,但你会看到一个 ownership_invalid_mutation 警告,因为该组件正在修改并不"属于"它的状态:

<!--- file: App.svelte --->
<script>
	import Child from './Child.svelte';

	let object = $state({count: 0});
</script>

<Child {object} />
<!--- file: Child.svelte --->
<script>
	let { object } = $props();
</script>

<button onclick={() => {
	// will cause the count below to update,
	// but with a warning. Don't mutate
	// objects you don't own!
	object.count += 1
}}>
	clicks: {object.count}
</button>

未使用 $bindable 声明的 prop,其回退值会原样保留——它不会被转换成响应式状态代理——这意味着修改它不会引起更新:

<!--- file: App.svelte --->
<script>
	import Child from './Child.svelte';
</script>

<Child />
<!--- file: Child.svelte --->
<script>
	let { object = { count: 0 } } = $props();
</script>

<button onclick={() => {
	// has no effect if the fallback value is used
	object.count += 1
}}>
	clicks: {object.count}
</button>

总结:不要修改 props。要么使用回调 props 来传递变更,要么——如果父子组件应当共享同一个对象——使用 $bindable 符文。

类型安全

你可以通过为 props 添加类型注解来获得类型安全,方式与注解其他任何变量声明一样。在 TypeScript 中看起来像这样……

<script lang="ts">
	let { adjective }: { adjective: string } = $props();
</script>

……而在 JSDoc 中则可以这样写:

<script>
	/** @type {{ adjective: string }} */
	let { adjective } = $props();
</script>

当然,你也可以将类型声明与注解分离:

<script lang="ts">
	interface Props {
		adjective: string;
	}

	let { adjective }: Props = $props();
</script>

[!NOTE] 原生 DOM 元素的接口由 svelte/elements 模块提供(详见为包装组件添加类型

如果你的组件暴露了像 children 这样的代码片段 props,应当使用从 'svelte' 导入的 Snippet 接口来为其标注类型——示例见为代码片段添加类型

建议添加类型,这样使用你组件的人就能轻松了解应当提供哪些 props。

$props.id()

这个在 5.20.0 版本新增的符文,会生成一个对当前组件实例唯一的 ID。在对服务端渲染的组件进行 hydration 时,该值在客户端与服务端之间保持一致。

这在通过 foraria-labelledby 等属性关联元素时非常有用。

<script>
	const uid = $props.id();
</script>

<form>
	<label for="{uid}-firstname">First Name: </label>
	<input id="{uid}-firstname" type="text" />

	<label for="{uid}-lastname">Last Name: </label>
	<input id="{uid}-lastname" type="text" />
</form>