Props与组件通信 #
一、Props 基础 #
1.1 定义 Props #
Svelte 4 语法:
svelte
<script>
export let name = 'World';
export let count = 0;
export let items = [];
</script>
<p>Hello {name}!</p>
<p>Count: {count}</p>
Svelte 5 语法:
svelte
<script>
let { name = 'World', count = 0, items = [] } = $props();
</script>
<p>Hello {name}!</p>
<p>Count: {count}</p>
1.2 传递 Props #
svelte
<script>
import Child from './Child.svelte';
</script>
<Child name="Svelte" count={42} items={[1, 2, 3]} />
<Child name="React" />
<Child />
1.3 展开 Props #
svelte
<script>
import Child from './Child.svelte';
let props = {
name: 'Vue',
count: 10,
items: ['a', 'b', 'c']
};
</script>
<Child {...props} />
二、Props 类型 #
2.1 基本类型 #
svelte
<script>
let {
str = '',
num = 0,
bool = false,
arr = [],
obj = {},
fn = () => {}
} = $props();
</script>
2.2 TypeScript 类型 #
svelte
<script lang="ts">
interface Props {
name: string;
age?: number;
items: string[];
onClick?: (id: number) => void;
}
let { name, age = 0, items, onClick }: Props = $props();
</script>
2.3 复杂类型 #
svelte
<script lang="ts">
interface User {
id: number;
name: string;
email: string;
}
interface Props {
user: User;
onUpdate: (user: User) => void;
}
let { user, onUpdate }: Props = $props();
</script>
三、双向绑定 #
3.1 bind:value #
父组件:
svelte
<script>
import Input from './Input.svelte';
let value = '';
</script>
<Input bind:value />
<p>Value: {value}</p>
子组件:
svelte
<script>
let { value = '' } = $props();
</script>
<input bind:value />
3.2 $bindable #
Svelte 5 使用 $bindable 声明可绑定属性:
svelte
<script>
let { value = $bindable('') } = $props();
</script>
<input bind:value />
3.3 多个双向绑定 #
svelte
<script>
let {
value = $bindable(''),
checked = $bindable(false),
items = $bindable([])
} = $props();
</script>
<input bind:value />
<input type="checkbox" bind:checked />
3.4 完整示例:表单组件 #
svelte
<script>
let {
email = $bindable(''),
password = $bindable(''),
remember = $bindable(false)
} = $props();
</script>
<div class="form-group">
<label>Email</label>
<input type="email" bind:value={email} />
</div>
<div class="form-group">
<label>Password</label>
<input type="password" bind:value={password} />
</div>
<label>
<input type="checkbox" bind:checked={remember} />
Remember me
</label>
四、组件通信方式 #
4.1 父传子(Props) #
svelte
<script>
import Child from './Child.svelte';
let message = 'Hello from parent';
</script>
<Child {message} />
4.2 子传父(回调函数) #
svelte
<script>
import Child from './Child.svelte';
function handleUpdate(data) {
console.log('Received from child:', data);
}
</script>
<Child onUpdate={handleUpdate} />
子组件:
svelte
<script>
let { onUpdate } = $props();
function sendData() {
onUpdate?.({ text: 'Hello from child' });
}
</script>
<button onclick={sendData}>Send to Parent</button>
4.3 自定义事件(Svelte 4) #
子组件:
svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('message', { text: 'Hello' });
}
</script>
<button on:click={handleClick}>Send Event</button>
父组件:
svelte
<script>
import Child from './Child.svelte';
function handleMessage(event) {
console.log(event.detail.text);
}
</script>
<Child on:message={handleMessage} />
4.4 Store 跨组件通信 #
javascript
import { writable } from 'svelte/store';
export const sharedState = writable({
user: null,
theme: 'light'
});
组件 A:
svelte
<script>
import { sharedState } from './stores.js';
</script>
<button onclick={() => $sharedState.theme = 'dark'}>
Set Dark Theme
</button>
组件 B:
svelte
<script>
import { sharedState } from './stores.js';
</script>
<p>Current theme: {$sharedState.theme}</p>
五、Context API #
5.1 设置 Context #
svelte
<script>
import { setContext } from 'svelte';
const theme = 'dark';
const user = { name: 'Alice' };
setContext('theme', theme);
setContext('user', user);
setContext('config', {
apiUrl: 'https://api.example.com',
version: '1.0.0'
});
</script>
<slot />
5.2 获取 Context #
svelte
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
const user = getContext('user');
const config = getContext('config');
</script>
<p>Theme: {theme}</p>
<p>User: {user.name}</p>
<p>API: {config.apiUrl}</p>
5.3 响应式 Context #
svelte
<script>
import { setContext } from 'svelte';
let theme = $state('light');
setContext('theme', {
get value() { return theme; },
toggle: () => {
theme = theme === 'light' ? 'dark' : 'light';
}
});
</script>
5.4 完整示例:主题提供者 #
ThemeProvider.svelte:
svelte
<script>
import { setContext } from 'svelte';
let theme = $state('light');
setContext('theme', {
get value() { return theme; },
set value(v) { theme = v; },
toggle: () => {
theme = theme === 'light' ? 'dark' : 'light';
}
});
</script>
<div data-theme={theme}>
<slot />
</div>
<style>
[data-theme="light"] {
--bg: #ffffff;
--text: #000000;
}
[data-theme="dark"] {
--bg: #1a1a1a;
--text: #ffffff;
}
</style>
ThemeToggle.svelte:
svelte
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
</script>
<button onclick={() => theme.toggle()}>
{theme.value === 'light' ? '🌙' : '☀️'}
</button>
App.svelte:
svelte
<script>
import ThemeProvider from './ThemeProvider.svelte';
import ThemeToggle from './ThemeToggle.svelte';
import Content from './Content.svelte';
</script>
<ThemeProvider>
<header>
<h1>My App</h1>
<ThemeToggle />
</header>
<Content />
</ThemeProvider>
六、Slot 插槽 #
6.1 基本插槽 #
svelte
<script>
let { } = $props();
</script>
<div class="card">
<slot />
</div>
使用:
svelte
<Card>
<p>This goes into the slot</p>
</Card>
6.2 具名插槽 #
svelte
<script>
let { } = $props();
</script>
<div class="card">
<header>
<slot name="header" />
</header>
<main>
<slot />
</main>
<footer>
<slot name="footer" />
</footer>
</div>
使用:
svelte
<Card>
<h2 slot="header">Title</h2>
<p>Main content goes here</p>
<p slot="footer">Footer content</p>
</Card>
6.3 插槽 Props #
svelte
<script>
let { items = [] } = $props();
</script>
<ul>
{#each items as item}
<li>
<slot {item} />
</li>
{/each}
</ul>
使用:
svelte
<List items={items} let:item>
<span>{item.name}: {item.value}</span>
</List>
6.4 Svelte 5 Snippet #
svelte
<script>
let { header, footer, children } = $props();
</script>
<div class="card">
<header>
{@render header?.()}
</header>
<main>
{@render children?.()}
</main>
<footer>
{@render footer?.()}
</footer>
</div>
使用:
svelte
<script>
import Card from './Card.svelte';
let header = snippet(() => <h2>Title</h2>);
let footer = snippet(() => <p>Footer</p>);
</script>
<Card {header} {footer}>
<p>Main content</p>
</Card>
七、组件组合模式 #
7.1 复合组件 #
svelte
<script>
import Tabs from './Tabs.svelte';
import Tab from './Tab.svelte';
import TabPanel from './TabPanel.svelte';
let activeTab = 0;
</script>
<Tabs bind:activeTab>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
<TabPanel>
Content for Tab 1
</TabPanel>
<TabPanel>
Content for Tab 2
</TabPanel>
<TabPanel>
Content for Tab 3
</TabPanel>
</Tabs>
7.2 Render Props 模式 #
svelte
<script>
let { data, render } = $props();
let loading = $state(true);
let result = $state(null);
$effect(() => {
data().then(r => {
result = r;
loading = false;
});
});
</script>
{#if loading}
<p>Loading...</p>
{:else}
{@render render(result)}
{/if}
使用:
svelte
<script>
import DataLoader from './DataLoader.svelte';
let renderData = snippet((data) => (
<ul>
{#each data as item}
<li>{item.name}</li>
{/each}
</ul>
));
</script>
<DataLoader
data={() => fetch('/api/items').then(r => r.json())}
render={renderData}
/>
7.3 高阶组件 #
svelte
<script>
import { getContext } from 'svelte';
let { component: Component, ...restProps } = $props();
const user = getContext('user');
</script>
{#if user}
<svelte:component this={Component} {...restProps} />
{:else}
<p>Please login</p>
{/if}
八、最佳实践 #
8.1 Props 命名规范 #
svelte
<script>
let {
title,
isVisible = true,
onToggle,
className = '',
style = {}
} = $props();
</script>
8.2 Props 验证 #
svelte
<script lang="ts">
interface Props {
id: number;
name: string;
email?: string;
role: 'admin' | 'user' | 'guest';
onAction: (action: string) => void;
}
let { id, name, email, role = 'user', onAction }: Props = $props();
</script>
8.3 避免 Props 透传 #
svelte
<script>
let {
title,
description,
...restProps
} = $props();
</script>
<article>
<h2>{title}</h2>
<p>{description}</p>
<div {...restProps}>
<slot />
</div>
</article>
九、完整示例:可复用表单组件 #
svelte
<script lang="ts">
interface Field {
name: string;
label: string;
type: string;
required?: boolean;
placeholder?: string;
}
interface Props {
fields: Field[];
values: Record<string, string>;
errors?: Record<string, string>;
onSubmit: (values: Record<string, string>) => void;
submitText?: string;
}
let {
fields,
values = $bindable({}),
errors = {},
onSubmit,
submitText = 'Submit'
}: Props = $props();
function handleSubmit(e: Event) {
e.preventDefault();
onSubmit(values);
}
</script>
<form onsubmit={handleSubmit}>
{#each fields as field}
<div class="form-group">
<label for={field.name}>
{field.label}
{#if field.required}<span class="required">*</span>{/if}
</label>
<input
id={field.name}
name={field.name}
type={field.type}
bind:value={values[field.name]}
placeholder={field.placeholder}
required={field.required}
class:error={errors[field.name]}
/>
{#if errors[field.name]}
<span class="error-message">{errors[field.name]}</span>
{/if}
</div>
{/each}
<button type="submit">{submitText}</button>
</form>
<style>
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.25rem;
}
.required {
color: red;
}
input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
input.error {
border-color: red;
}
.error-message {
color: red;
font-size: 0.8rem;
}
</style>
使用:
svelte
<script>
import Form from './Form.svelte';
let fields = [
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'password', label: 'Password', type: 'password', required: true },
{ name: 'remember', label: 'Remember Me', type: 'checkbox' }
];
let values = {};
let errors = {};
function handleSubmit(values) {
console.log('Form submitted:', values);
}
</script>
<Form
{fields}
bind:values
{errors}
onSubmit={handleSubmit}
submitText="Login"
/>
十、总结 #
| 方式 | 方向 | 使用场景 |
|---|---|---|
| Props | 父→子 | 数据传递 |
| bind:prop | 双向 | 表单、状态同步 |
| 回调函数 | 子→父 | 事件通知 |
| 自定义事件 | 子→父 | Svelte 4 事件系统 |
| Store | 任意 | 全局状态 |
| Context | 祖先→后代 | 深层嵌套共享 |
组件通信要点:
- Props 用于父传子
$bindable实现双向绑定- 回调函数用于子传父
- Store 用于跨组件共享
- Context 用于深层嵌套
- Slot 用于内容分发
最后更新:2026-03-28