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