SvelteKit 的 +layout.svelte 为什么不能直接向页面传递 props?
结论
+layout.svelte 接收的 props 不会自动成为 +page.svelte 的 props,{@render children()} 也没有向路由页面传入任意 props 的接口。布局和页面仍可以共享数据:布局 load 返回的数据会传给下级页面,布局组件内部的交互状态则可以通过 Context API 共享。SvelteKit 路由文档、状态管理文档
下面的组件示例使用 Svelte 5 的 $props()、$state 和 {@render ...} 语法。
为什么不能像普通组件一样传递 props?
$props() 用来读取当前组件收到的属性,不会把属性自动传给后代。普通 Svelte 组件也需要显式传递属性,例如 <Child value={value} />。
SvelteKit 根据路由组合页面和嵌套布局。布局中没有直接编写 <Page ... />,而是通过框架提供的 children snippet 渲染下级内容。普通 snippet 可以声明参数,但 SvelteKit 提供的路由 children 接口不接收参数,因此不能用 {@render children(value)} 向页面传递 value prop。布局接口说明
布局组件负责公共 UI,load 负责路由数据,Context 负责组件树中的共享状态。这些接口的分工可以解释实际用法,但不能由此推断框架设计者选择这一方案的全部动机。
共享页面所需的数据:使用布局 load
如果数据需要在渲染页面前获取,可以将它放在 +layout.js 或 +layout.server.js 的 load 中。下面用固定值演示数据流。
src/routes/+layout.js:
export function load() {
return {
siteName: 'Demo'
};
}
src/routes/+page.svelte:
<script>
let { data } = $props();
</script>
<h1>{data.siteName}</h1>
页面可以直接读取 data.siteName,无需布局通过 children 转发,也不必为此额外创建 +page.js。
当页面自己的 load 需要使用上级数据进行计算或请求时,需要在该函数中调用 await parent()。下级 load 返回同名字段时,下级值会覆盖上级值,因此应避免无意间重名。布局数据与 parent() 文档
共享布局中的交互状态:使用 Context
如果要共享在 +layout.svelte 内创建的可变状态,例如展开状态或选择结果,可以使用 setContext 和 getContext。Context 的作用域是组件树,无需建立全局共享变量。
以下计数器示例独立于上一个示例。
src/routes/+layout.svelte:
<script>
import { setContext } from 'svelte';
let { children } = $props();
const counter = $state({ count: 0 });
setContext('counter', counter);
</script>
<p>布局中的计数:{counter.count}</p>
{@render children()}
src/routes/+page.svelte:
<script>
import { getContext } from 'svelte';
const counter = getContext('counter');
</script>
<button onclick={() => counter.count += 1}>
页面中的计数:{counter.count}
</button>
这里共享的是同一个 $state 对象。页面修改 counter.count 后,读取该属性的布局和页面都会更新。setContext 和 getContext 应在组件初始化时调用,页面也必须位于提供该 Context 的布局之下。Context 与共享状态说明
使用时注意什么?
- 将
load数据赋值给布局内的普通变量,不会自动把这个新变量加入页面的data。 - Context 中直接存入某个原始值,不等于建立持续同步关系。需要共享变化时,可以使用响应式对象或读取当前值的函数。
- 启用 SSR 时,不要用模块级可变单例保存用户专属状态,服务器进程中的变量可能被多个请求共享。服务器共享状态注意事项
如果只需要给自己直接渲染的普通组件传数据,继续使用 props 即可。
备注:内容仅供参考。
