Context API #

一、Context 概述 #

Context API 用于在组件树中共享数据,避免 Props 逐层传递(prop drilling)。

text
┌─────────────────────────────────────────────────────────────┐
│                    Context 工作原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  传统 Props 传递                                             │
│  ┌─────────┐                                                │
│  │  App    │ data                                          │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │ Header  │                                                │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │  Nav    │                                                │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │ UserMenu│ ← 需要data                                     │
│  └─────────┘                                                │
│                                                             │
│  Context 方式                                                │
│  ┌─────────┐                                                │
│  │  App    │ setContext('data', data)                      │
│  └────┬────┘                                                │
│       ↓                                                     │
│  ┌─────────┐                                                │
│  │ Header  │                                                │
│  └────┬────┘                                                │
│       ↓                                                     │
│  ┌─────────┐                                                │
│  │  Nav    │                                                │
│  └────┬────┘                                                │
│       ↓                                                     │
│  ┌─────────┐                                                │
│  │ UserMenu│ getContext('data') ← 直接获取                  │
│  └─────────┘                                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、基本用法 #

2.1 设置 Context #

svelte
<script>
  import { setContext } from 'svelte';
  
  const theme = 'dark';
  const user = { name: 'Alice', role: 'admin' };
  
  setContext('theme', theme);
  setContext('user', user);
</script>

<slot />

2.2 获取 Context #

svelte
<script>
  import { getContext } from 'svelte';
  
  const theme = getContext('theme');
  const user = getContext('user');
</script>

<p>Theme: {theme}</p>
<p>User: {user.name}</p>

2.3 完整示例 #

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: #333333;
    --primary: #ff3e00;
  }
  
  [data-theme="dark"] {
    --bg: #1a1a1a;
    --text: #ffffff;
    --primary: #ff6e40;
  }
</style>

ThemeButton.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  const theme = getContext('theme');
</script>

<button onclick={() => theme.toggle()}>
  {theme.value === 'light' ? '🌙 Dark' : '☀️ Light'}
</button>

App.svelte:

svelte
<script>
  import ThemeProvider from './ThemeProvider.svelte';
  import ThemeButton from './ThemeButton.svelte';
  import Content from './Content.svelte';
</script>

<ThemeProvider>
  <header>
    <h1>My App</h1>
    <ThemeButton />
  </header>
  <Content />
</ThemeProvider>

三、Context 特点 #

3.1 作用域 #

Context 只在设置它的组件的后代组件中可用:

svelte
<script>
  import { setContext, getContext } from 'svelte';
  import Child from './Child.svelte';
  
  setContext('message', 'Hello from Parent');
  
  const msg = getContext('message');
  console.log(msg);
</script>

<Child />

3.2 键名建议 #

使用 Symbol 作为键名避免冲突:

javascript
const THEME_KEY = Symbol('theme');
const USER_KEY = Symbol('user');
const CONFIG_KEY = Symbol('config');
svelte
<script>
  import { setContext } from 'svelte';
  
  const THEME_KEY = Symbol('theme');
  
  setContext(THEME_KEY, 'dark');
</script>

3.3 默认值处理 #

svelte
<script>
  import { getContext } from 'svelte';
  
  const theme = getContext('theme') || 'light';
</script>

四、响应式 Context #

4.1 使用 Getter #

svelte
<script>
  import { setContext } from 'svelte';
  
  let count = $state(0);
  
  setContext('counter', {
    get count() { return count; },
    increment: () => { count += 1; },
    decrement: () => { count -= 1; }
  });
</script>

<slot />

4.2 使用 Store #

svelte
<script>
  import { setContext } from 'svelte';
  import { writable } from 'svelte/store';
  
  const theme = writable('light');
  
  setContext('theme', theme);
</script>

<slot />

子组件:

svelte
<script>
  import { getContext } from 'svelte';
  
  const theme = getContext('theme');
</script>

<button onclick={() => theme.set($theme === 'light' ? 'dark' : 'light')}>
  Toggle Theme
</button>

4.3 使用类 #

svelte
<script>
  import { setContext } from 'svelte';
  
  class ThemeManager {
    theme = $state('light');
    
    toggle() {
      this.theme = this.theme === 'light' ? 'dark' : 'light';
    }
  }
  
  const manager = new ThemeManager();
  setContext('theme', manager);
</script>

<slot />

五、实际应用示例 #

5.1 表单 Context #

Form.svelte:

svelte
<script>
  import { setContext } from 'svelte';
  
  let values = $state({});
  let errors = $state({});
  let touched = $state({});
  
  function setField(name, value) {
    values = { ...values, [name]: value };
  }
  
  function setTouched(name) {
    touched = { ...touched, [name]: true };
  }
  
  function setError(name, error) {
    errors = { ...errors, [name]: error };
  }
  
  function validate() {
    return Object.keys(errors).length === 0;
  }
  
  setContext('form', {
    get values() { return values; },
    get errors() { return errors; },
    get touched() { return touched; },
    setField,
    setTouched,
    setError,
    validate
  });
</script>

<form>
  <slot />
</form>

FormField.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  let { name, label, type = 'text' } = $props();
  
  const form = getContext('form');
  
  let value = $derived(form.values[name] || '');
  let error = $derived(form.errors[name]);
  let isTouched = $derived(form.touched[name]);
</script>

<div class="form-field">
  <label>{label}</label>
  <input 
    type={type}
    bind:value
    onblur={() => form.setTouched(name)}
    oninput={() => form.setField(name, value)}
  />
  {#if isTouched && error}
    <span class="error">{error}</span>
  {/if}
</div>

使用:

svelte
<script>
  import Form from './Form.svelte';
  import FormField from './FormField.svelte';
</script>

<Form>
  <FormField name="email" label="Email" type="email" />
  <FormField name="password" label="Password" type="password" />
  <button type="submit">Submit</button>
</Form>

5.2 导航 Context #

Navigation.svelte:

svelte
<script>
  import { setContext } from 'svelte';
  
  let items = [];
  let activeId = $state(null);
  
  function register(id) {
    items = [...items, id];
    if (!activeId) activeId = id;
    return items.indexOf(id);
  }
  
  function unregister(id) {
    items = items.filter(i => i !== id);
  }
  
  function activate(id) {
    activeId = id;
  }
  
  setContext('navigation', {
    register,
    unregister,
    activate,
    get activeId() { return activeId; }
  });
</script>

<nav>
  <slot />
</nav>

NavItem.svelte:

svelte
<script>
  import { getContext, onMount } from 'svelte';
  
  let { id, href } = $props();
  
  const nav = getContext('navigation');
  
  onMount(() => {
    nav.register(id);
    return () => nav.unregister(id);
  });
</script>

<a 
  href={href}
  class:active={nav.activeId === id}
  onclick={() => nav.activate(id)}
>
  <slot />
</a>

5.3 国际化 Context #

I18n.svelte:

svelte
<script>
  import { setContext } from 'svelte';
  
  const translations = {
    en: {
      hello: 'Hello',
      goodbye: 'Goodbye',
      submit: 'Submit'
    },
    zh: {
      hello: '你好',
      goodbye: '再见',
      submit: '提交'
    }
  };
  
  let locale = $state('en');
  
  function t(key) {
    return translations[locale]?.[key] || key;
  }
  
  function setLocale(newLocale) {
    locale = newLocale;
  }
  
  setContext('i18n', {
    get locale() { return locale; },
    t,
    setLocale
  });
</script>

<slot />

Translate.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  let { key } = $props();
  
  const i18n = getContext('i18n');
</script>

<span>{i18n.t(key)}</span>

使用:

svelte
<script>
  import I18n from './I18n.svelte';
  import Translate from './Translate.svelte';
</script>

<I18n>
  <p><Translate key="hello" /></p>
  <p><Translate key="goodbye" /></p>
  <button><Translate key="submit" /></button>
</I18n>

六、Context vs Store #

6.1 对比 #

特性 Context Store
作用域 组件树 全局
响应式 需要额外处理 内置
适用场景 局部共享 全局状态
访问方式 getContext $ 语法

6.2 选择建议 #

text
使用 Context:
├── 主题、配置等局部共享
├── 表单状态
├── 组件库内部通信
└── 避免全局污染

使用 Store:
├── 用户信息
├── 全局通知
├── 购物车
└── 跨页面共享状态

七、最佳实践 #

7.1 封装 Context #

javascript
export const ThemeKey = Symbol('theme');

export function createThemeContext() {
  let theme = $state('light');
  
  return {
    get theme() { return theme; },
    set theme(v) { theme = v; },
    toggle: () => {
      theme = theme === 'light' ? 'dark' : 'light';
    }
  };
}
svelte
<script>
  import { setContext } from 'svelte';
  import { ThemeKey, createThemeContext } from './theme-context.js';
  
  setContext(ThemeKey, createThemeContext());
</script>

<slot />

7.2 类型安全 #

svelte
<script lang="ts">
  import { setContext, getContext } from 'svelte';
  
  interface ThemeContext {
    theme: 'light' | 'dark';
    toggle: () => void;
  }
  
  declare const THEME_KEY: unique symbol;
  
  setContext<THEME_KEY, ThemeContext>(THEME_KEY, {
    theme: 'light',
    toggle: () => {}
  });
  
  const ctx = getContext<THEME_KEY, ThemeContext>(THEME_KEY);
</script>

7.3 错误处理 #

svelte
<script>
  import { getContext } from 'svelte';
  
  function useTheme() {
    const theme = getContext('theme');
    
    if (!theme) {
      throw new Error('useTheme must be used within ThemeProvider');
    }
    
    return theme;
  }
  
  const theme = useTheme();
</script>

八、完整示例:标签页组件 #

Tabs.svelte:

svelte
<script>
  import { setContext } from 'svelte';
  
  let { defaultTab = 0 } = $props();
  
  let activeTab = $state(defaultTab);
  
  function selectTab(index) {
    activeTab = index;
  }
  
  setContext('tabs', {
    get activeTab() { return activeTab; },
    selectTab
  });
</script>

<div class="tabs">
  <slot />
</div>

TabList.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  const tabs = getContext('tabs');
</script>

<div class="tab-list" role="tablist">
  <slot />
</div>

Tab.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  let { index } = $props();
  
  const tabs = getContext('tabs');
</script>

<button 
  role="tab"
  aria-selected={tabs.activeTab === index}
  class:active={tabs.activeTab === index}
  onclick={() => tabs.selectTab(index)}
>
  <slot />
</button>

TabPanel.svelte:

svelte
<script>
  import { getContext } from 'svelte';
  
  let { index } = $props();
  
  const tabs = getContext('tabs');
</script>

{#if tabs.activeTab === index}
  <div role="tabpanel" class="tab-panel">
    <slot />
  </div>
{/if}

使用:

svelte
<script>
  import Tabs from './Tabs.svelte';
  import TabList from './TabList.svelte';
  import Tab from './Tab.svelte';
  import TabPanel from './TabPanel.svelte';
</script>

<Tabs>
  <TabList>
    <Tab index={0}>Tab 1</Tab>
    <Tab index={1}>Tab 2</Tab>
    <Tab index={2}>Tab 3</Tab>
  </TabList>
  
  <TabPanel index={0}>Content 1</TabPanel>
  <TabPanel index={1}>Content 2</TabPanel>
  <TabPanel index={2}>Content 3</TabPanel>
</Tabs>

九、总结 #

函数 说明
setContext(key, value) 设置 Context
getContext(key) 获取 Context
hasContext(key) 检查 Context 是否存在

Context API 要点:

  • 使用 setContext 设置,getContext 获取
  • Context 只在后代组件中可用
  • 使用 Symbol 作为键名避免冲突
  • 结合响应式实现动态更新
  • 适合组件树内部共享数据
  • 与 Store 配合处理全局状态
最后更新:2026-03-28