组件通信方式汇总
本教程共 50 篇 · 第 37 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
本节目标:一次性理清 Svelte 5 的六种组件通信方式,知道每种方式适合什么场景,遇到实际需求能快速选对方案。
为什么需要这么多方式
组件不会孤立存在,它们需要交换数据。父组件要把数据传给子组件,子组件要通知父组件发生了事件,深层嵌套的组件要共享状态。不同的场景需要不同的通信手段,选对了代码简洁,选错了到处打补丁。
Svelte 5 提供了以下几种方式:
| 方式 | 数据流向 | 适用场景 |
|---|---|---|
| Props | 父 → 子 | 传递数据给直接子组件 |
| 回调 Props | 子 → 父 | 子组件通知父组件 |
$bindable | 父 ⇌ 子 | 双向绑定表单值 |
| Context | 祖先 → 后代 | 跨多层传递,避免逐层传 |
| Store | 全局 | 复杂异步数据流 |
全局 $state | 全局 | 简单共享状态 |
Props:父传子
最基础的通信方式。父组件通过属性把数据传给子组件,子组件用 $props() 接收:
<!-- App.svelte -->
<script>
import Greeting from './Greeting.svelte';
</script>
<Greeting name="张三" />
<!-- Greeting.svelte -->
<script>
let { name } = $props();
</script>
<h1>你好,{name}!</h1>
Props 可以设默认值,也可以重命名:
// 默认值
let { count = 0 } = $props();
// 重命名(比如 prop 名是 JS 关键字)
let { class: className = '' } = $props();
// 收集剩余 props
let { id, ...rest } = $props();
NoteProps 是单向的:父变则子更新,但子组件不应该直接修改传入的 prop。需要修改时用回调 props 或
$bindable。
回调 Props:子传父
子组件不能直接修改父组件的数据,但可以调用父组件传下来的函数来”通知”:
<!-- App.svelte -->
<script>
import Counter from './Counter.svelte';
let total = $state(0);
function handleIncrement() {
total++;
}
</script>
<Counter onIncrement={handleIncrement} />
<p>总计:{total}</p>
<!-- Counter.svelte -->
<script>
let { onIncrement } = $props();
</script>
<button onclick={onIncrement}>+1</button>
父组件把 handleIncrement 函数作为 onIncrement prop 传下去。子组件点击按钮时调用它,父组件的 total 就会增加。
Tip这种模式本质上和 React 的回调 props 一样。命名习惯是用
on开头,如onChange、onSubmit、onSelect。
$bindable:双向绑定
有时候父和子需要共享同一个值,父子两边都能改。用 $bindable 声明一个可绑定的 prop:
<!-- FancyInput.svelte -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value={value} />
父组件用 bind: 指令绑定:
<!-- App.svelte -->
<script>
import FancyInput from './FancyInput.svelte';
let name = $state('张三');
</script>
<FancyInput bind:value={name} />
<p>你输入了:{name}</p>
子组件里的 <input> 改了值,父组件的 name 同步更新;父组件改了 name,子组件的 input 也同步更新。
Note
$bindable要谨慎使用。过度使用双向绑定会让数据流向难以追踪。大多数场景用回调 props 更清晰,只有在表单控件这类”值天然需要同步”的场景才推荐用bind:。
Context:跨层传递
当组件嵌套很深时,逐层传 props 很痛苦(叫”prop drilling”)。Context 让祖先组件把数据”挂”到组件树上,任意层级的后代都能直接取:
// context.ts
import { createContext } from 'svelte';
interface User {
name: string;
}
export const [getUserContext, setUserContext] = createContext<User>();
<!-- Parent.svelte -->
<script>
import { setUserContext } from './context';
let { children } = $props();
setUserContext({ name: '张三' });
</script>
{@render children()}
<!-- DeepChild.svelte -->
<script>
import { getUserContext } from './context';
const user = getUserContext();
</script>
<p>你好 {user.name}</p>
Context 的特点:
- 只能从祖先传给后代,不能逆流
- 不需要逐层传递,中间组件甚至不知道有这个数据
- 可以存响应式状态(
$state对象),后代能感知变化
NoteSvelte 5.40 起推荐用
createContext替代旧的setContext/getContext,类型更安全。如果你的版本低于 5.40,用setContext(key, value)和getContext(key)。
Context 中存响应式状态时要注意:修改对象的属性可以,但重新赋值整个对象会断开链接:
// 正确:修改属性
counter.count = 0;
// 错误:重新赋值会断开引用
counter = { count: 0 };
Store:全局状态
Store 是 Svelte 的传统响应式方案。在 Svelte 5 中,大部分场景可以用全局 $state 替代,但处理复杂异步流时 Store 仍然好用:
// store.js
import { writable } from 'svelte/store';
export const theme = writable('light');
<script>
import { theme } from './store.js';
</script>
<!-- $ 前缀自动订阅 -->
<p>当前主题:{$theme}</p>
<button onclick={() => $theme = 'dark'}>切换暗色</button>
$ 前缀会自动订阅和取消订阅,赋值时调用 store 的 set 方法。
全局 $state:简单共享
Svelte 5 中最简单的全局状态方案。把 $state 放在 .svelte.js 文件里导出,任何组件都能用:
// state.svelte.ts
export const userState = $state({
name: '张三',
age: 25
});
<script>
import { userState } from './state.svelte.ts';
</script>
<p>{userState.name},{userState.age} 岁</p>
<button onclick={() => userState.age++}>长大一岁</button>
Tip简单的全局状态用
$state就够了。只有当你需要复杂的数据流(如防抖、节流、多源合并)时,才考虑用 Store。
如何选择
用一个流程来帮你决策:
- 父子直接通信 → Props + 回调 Props
- 表单控件双向同步 →
$bindable - 跨 3 层以上传递 → Context
- 多个不相关组件共享状态 → 全局
$state - 复杂异步数据流 → Store
Note别把简单问题搞复杂。两个相邻组件之间通信,用 Props 就行,别上 Context 或 Store。选最简单能解决问题的方案。
本节回顾
- Props 是基础通信方式,父传子,用
$props()接收 - 回调 Props 让子组件通过函数调用通知父组件
$bindable实现双向绑定,适合表单控件场景- Context 用
createContext创建,解决跨层传递问题 - Store 适合复杂异步流,简单场景用全局
$state即可 - 选择原则:越简单越好,能用 Props 就别用 Context