Context上下文 #

一、Context 概述 #

1.1 什么是 Context #

Context 提供了一种在组件树中共享数据的方式,无需逐层传递 props。

text
┌─────────────────────────────────────────────────────────────┐
│                    Context 解决的问题                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Props 传递问题                                            │
│   ┌─────────┐                                               │
│   │  App    │                                               │
│   │  theme  │                                               │
│   └────┬────┘                                               │
│        ↓ props                                              │
│   ┌─────────┐                                               │
│   │ Layout  │ (不需要 theme)                                │
│   └────┬────┘                                               │
│        ↓ props                                              │
│   ┌─────────┐                                               │
│   │ Sidebar │ (不需要 theme)                                │
│   └────┬────┘                                               │
│        ↓ props                                              │
│   ┌─────────┐                                               │
│   │ Button  │ ← 需要 theme                                  │
│   └─────────┘                                               │
│                                                             │
│   Context 解决方案                                          │
│   ┌─────────────────────────────────────────┐              │
│   │ App (Provider)                          │              │
│   │ ┌─────────┐ ┌─────────┐ ┌─────────┐    │              │
│   │ │ Layout  │ │ Sidebar │ │ Button  │    │              │
│   │ └─────────┘ └─────────┘ │ useContext │   │              │
│   │                         └─────────┘    │              │
│   └─────────────────────────────────────────┘              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 Context API #

API 说明
createContext 创建上下文
Provider 提供上下文值
useContext 消费上下文值

二、基本用法 #

2.1 创建 Context #

jsx
import { createContext } from 'solid-js';

// 创建一个 Context,可以提供默认值
const ThemeContext = createContext('light');
const UserContext = createContext(null);

2.2 提供 Context #

jsx
import { createContext } from 'solid-js';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = createSignal('dark');

  return (
    <ThemeContext.Provider value={theme()}>
      <Layout />
    </ThemeContext.Provider>
  );
}

2.3 消费 Context #

jsx
import { useContext } from 'solid-js';

function Button() {
  const theme = useContext(ThemeContext);

  return (
    <button class={`btn btn-${theme}`}>
      Click me
    </button>
  );
}

2.4 完整示例 #

jsx
import { createContext, useContext, createSignal } from 'solid-js';

// 1. 创建 Context
const ThemeContext = createContext();

// 2. Provider 组件
function App() {
  const [theme, setTheme] = createSignal('light');

  const toggleTheme = () => {
    setTheme(t => t === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

// 3. 消费 Context
function Layout() {
  return (
    <div>
      <Header />
      <Main />
    </div>
  );
}

function Header() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <header class={theme()}>
      <button onClick={toggleTheme}>
        Toggle Theme
      </button>
    </header>
  );
}

function Main() {
  const { theme } = useContext(ThemeContext);

  return (
    <main class={theme()}>
      <Button>Click me</Button>
    </main>
  );
}

function Button(props) {
  const { theme } = useContext(ThemeContext);

  return (
    <button class={`btn btn-${theme()}`}>
      {props.children}
    </button>
  );
}

三、Context 模式 #

3.1 状态管理模式 #

jsx
// contexts/CounterContext.jsx
import { createContext, useContext, createSignal } from 'solid-js';

const CounterContext = createContext();

export function CounterProvider(props) {
  const [count, setCount] = createSignal(0);

  const value = {
    count,
    increment: () => setCount(c => c + 1),
    decrement: () => setCount(c => c - 1),
    reset: () => setCount(0)
  };

  return (
    <CounterContext.Provider value={value}>
      {props.children}
    </CounterContext.Provider>
  );
}

export function useCounter() {
  const context = useContext(CounterContext);
  if (!context) {
    throw new Error('useCounter must be used within CounterProvider');
  }
  return context;
}
jsx
// App.jsx
import { CounterProvider } from './contexts/CounterContext';

function App() {
  return (
    <CounterProvider>
      <Counter />
      <Controls />
    </CounterProvider>
  );
}

function Counter() {
  const { count } = useCounter();
  return <p>Count: {count()}</p>;
}

function Controls() {
  const { increment, decrement, reset } = useCounter();
  return (
    <div>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
      <button onClick={increment}>+</button>
    </div>
  );
}

3.2 主题模式 #

jsx
// contexts/ThemeContext.jsx
import { createContext, useContext, createSignal, createMemo } from 'solid-js';

const ThemeContext = createContext();

const themes = {
  light: {
    background: '#ffffff',
    text: '#000000',
    primary: '#007bff'
  },
  dark: {
    background: '#1a1a1a',
    text: '#ffffff',
    primary: '#4da3ff'
  }
};

export function ThemeProvider(props) {
  const [themeName, setThemeName] = createSignal(
    localStorage.getItem('theme') || 'light'
  );

  const theme = createMemo(() => themes[themeName()]);

  const toggleTheme = () => {
    setThemeName(t => {
      const newTheme = t === 'light' ? 'dark' : 'light';
      localStorage.setItem('theme', newTheme);
      return newTheme;
    });
  };

  return (
    <ThemeContext.Provider value={{ theme, themeName, toggleTheme }}>
      {props.children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}
jsx
// 使用
function App() {
  return (
    <ThemeProvider>
      <ThemedApp />
    </ThemeProvider>
  );
}

function ThemedApp() {
  const { theme } = useTheme();

  return (
    <div style={{
      'background-color': theme().background,
      color: theme().text
    }}>
      <Header />
      <Main />
    </div>
  );
}

function ThemeToggle() {
  const { themeName, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme}>
      Switch to {themeName() === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
}

3.3 用户认证模式 #

jsx
// contexts/AuthContext.jsx
import { createContext, useContext, createSignal, createEffect } from 'solid-js';

const AuthContext = createContext();

export function AuthProvider(props) {
  const [user, setUser] = createSignal(null);
  const [loading, setLoading] = createSignal(true);

  createEffect(() => {
    // 检查本地存储的 token
    const token = localStorage.getItem('token');
    if (token) {
      fetchUser(token).then(userData => {
        setUser(userData);
        setLoading(false);
      });
    } else {
      setLoading(false);
    }
  });

  const login = async (credentials) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials)
    });
    const { token, user } = await response.json();
    localStorage.setItem('token', token);
    setUser(user);
  };

  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {props.children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
jsx
// 使用
function App() {
  return (
    <AuthProvider>
      <Routes />
    </AuthProvider>
  );
}

function Routes() {
  const { user, loading } = useAuth();

  return (
    <Show when={!loading()} fallback={<p>Loading...</p>}>
      <Switch>
        <Match when={user()}>
          <Dashboard />
        </Match>
        <Match when={!user()}>
          <LoginPage />
        </Match>
      </Switch>
    </Show>
  );
}

function LoginPage() {
  const { login } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    await login({ username, password });
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

function Dashboard() {
  const { user, logout } = useAuth();

  return (
    <div>
      <h1>Welcome, {user().name}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

四、嵌套 Context #

4.1 多层 Context #

jsx
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <NotificationProvider>
          <Router>
            <Routes />
          </Router>
        </NotificationProvider>
      </AuthProvider>
    </ThemeProvider>
  );
}

4.2 Context 组合 #

jsx
function useAppContext() {
  const theme = useTheme();
  const auth = useAuth();
  const notifications = useNotifications();

  return { theme, auth, notifications };
}

function MyComponent() {
  const { theme, auth, notifications } = useAppContext();

  // ...
}

4.3 覆盖 Context 值 #

jsx
function App() {
  return (
    <ThemeContext.Provider value="light">
      <Layout />
      <ThemeContext.Provider value="dark">
        <Sidebar /> {/* 使用 dark 主题 */}
      </ThemeContext.Provider>
    </ThemeContext.Provider>
  );
}

五、高级用法 #

5.1 带默认值的 Context #

jsx
const ThemeContext = createContext({
  theme: () => 'light',
  toggleTheme: () => {}
});

function MaybeWithProvider() {
  // 如果没有 Provider,使用默认值
  const { theme } = useContext(ThemeContext);
  return <p>Theme: {theme()}</p>;
}

5.2 动态 Context #

jsx
function createDynamicContext(factory) {
  const Context = createContext();

  return {
    Provider: (props) => {
      const value = factory(props);
      return (
        <Context.Provider value={value}>
          {props.children}
        </Context.Provider>
      );
    },
    useContext: () => useContext(Context)
  };
}

// 使用
const UserContext = createDynamicContext((props) => {
  const [user, setUser] = createSignal(props.initialUser);
  return { user, setUser };
});

5.3 Context with Selector #

jsx
function useContextSelector(Context, selector) {
  const context = useContext(Context);
  return createMemo(() => selector(context));
}

// 使用
function UserName() {
  const name = useContextSelector(UserContext, ctx => ctx.user()?.name);
  return <p>{name()}</p>;
}

六、最佳实践 #

6.1 Context 文件组织 #

text
src/
├── contexts/
│   ├── ThemeContext.jsx
│   ├── AuthContext.jsx
│   └── NotificationContext.jsx
├── components/
│   └── ...
└── App.jsx

6.2 自定义 Hook 封装 #

jsx
// contexts/ThemeContext.jsx
const ThemeContext = createContext();

export function ThemeProvider(props) {
  // ...
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

6.3 避免过度使用 #

jsx
// 不推荐:所有状态都放 Context
function App() {
  return (
    <StateProvider>
      <App />
    </StateProvider>
  );
}

// 推荐:只在需要跨组件共享时使用
function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <App />
      </ThemeProvider>
    </AuthProvider>
  );
}

6.4 性能优化 #

jsx
// 拆分 Context 减少不必要的渲染
function App() {
  return (
    <UserProvider>
      <ThemeProvider>
        <NotificationProvider>
          <App />
        </NotificationProvider>
      </ThemeProvider>
    </UserProvider>
  );
}

// 使用 selector 避免订阅整个 context
function UserName() {
  const { user } = useAuth();
  // 只订阅 user,不订阅其他 auth 状态
  return <span>{user()?.name}</span>;
}

七、总结 #

7.1 Context API #

API 说明
createContext(defaultValue) 创建上下文
Context.Provider 提供上下文值
useContext(Context) 消费上下文值

7.2 使用场景 #

场景 是否使用 Context
主题切换 ✅ 推荐
用户认证 ✅ 推荐
国际化 ✅ 推荐
父子组件通信 ❌ 使用 Props
兄弟组件通信 ✅ 可以使用
全局状态 ✅ 推荐

7.3 最佳实践 #

  1. 合理拆分:按功能拆分多个 Context
  2. 封装 Hook:提供 useXxx 函数
  3. 错误处理:检查 Context 是否存在
  4. 默认值:提供合理的默认值
  5. 性能优化:避免不必要的订阅
最后更新:2026-03-28