Context API #

一、Context 概述 #

1.1 什么是 Context #

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

text
App (Provider)
├── Header
│   └── ThemeButton  ← 可访问 Theme
├── Main
│   └── Content
│       └── ThemeToggle  ← 可访问 Theme
└── Footer

1.2 使用场景 #

场景 说明
主题 深色/浅色模式
用户 当前登录用户
语言 国际化设置
配置 应用配置

二、基本用法 #

2.1 创建 Context #

jsx
import { createContext } from 'preact';

// 创建带默认值的 Context
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const LanguageContext = createContext('zh-CN');

2.2 Provider 提供数据 #

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

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

2.3 useContext 消费数据 #

jsx
import { useContext } from 'preact';

function ThemedButton() {
  const theme = useContext(ThemeContext);
  
  return (
    <button class={`btn btn-${theme}`}>
      Themed Button
    </button>
  );
}

三、完整示例 #

3.1 主题切换 #

jsx
import { createContext, useContext, useState } from 'preact';

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

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

  const value = {
    theme,
    setTheme,
    toggleTheme,
    isDark: theme === 'dark'
  };

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

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

// 使用
function App() {
  return (
    <ThemeProvider>
      <Layout />
    </ThemeProvider>
  );
}

function Layout() {
  const { theme, toggleTheme } = useTheme();

  return (
    <div class={`layout theme-${theme}`}>
      <Header />
      <button onClick={toggleTheme}>
        Toggle Theme
      </button>
      <Main />
    </div>
  );
}

function Header() {
  const { isDark } = useTheme();

  return (
    <header style={{ background: isDark ? '#333' : '#fff' }}>
      <h1>My App</h1>
    </header>
  );
}

3.2 用户认证 #

jsx
const AuthContext = createContext(null);

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 检查登录状态
    checkAuth().then(user => {
      setUser(user);
      setLoading(false);
    });
  }, []);

  const login = async (credentials) => {
    const user = await loginAPI(credentials);
    setUser(user);
    return user;
  };

  const logout = async () => {
    await logoutAPI();
    setUser(null);
  };

  const value = {
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user
  };

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

function useAuth() {
  return useContext(AuthContext);
}

// 使用
function App() {
  return (
    <AuthProvider>
      <Router>
        <Home path="/" />
        <Login path="/login" />
        <ProtectedRoute path="/dashboard" component={Dashboard} />
      </Router>
    </AuthProvider>
  );
}

function ProtectedRoute({ component: Component, ...props }) {
  const { isAuthenticated, loading } = useAuth();

  if (loading) return <Loading />;
  if (!isAuthenticated) return <Redirect to="/login" />;

  return <Component {...props} />;
}

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

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

3.3 多语言支持 #

jsx
const i18n = {
  'zh-CN': {
    home: '首页',
    about: '关于',
    contact: '联系我们'
  },
  'en-US': {
    home: 'Home',
    about: 'About',
    contact: 'Contact'
  }
};

const LanguageContext = createContext(null);

function LanguageProvider({ children }) {
  const [language, setLanguage] = useState('zh-CN');

  const t = (key) => {
    return i18n[language][key] || key;
  };

  const value = {
    language,
    setLanguage,
    t
  };

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

function useLanguage() {
  return useContext(LanguageContext);
}

// 使用
function Navigation() {
  const { t, language, setLanguage } = useLanguage();

  return (
    <nav>
      <a href="/">{t('home')}</a>
      <a href="/about">{t('about')}</a>
      <a href="/contact">{t('contact')}</a>
      
      <select 
        value={language} 
        onChange={(e) => setLanguage(e.target.value)}
      >
        <option value="zh-CN">中文</option>
        <option value="en-US">English</option>
      </select>
    </nav>
  );
}

四、嵌套 Context #

4.1 多个 Provider #

jsx
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <LanguageProvider>
          <Layout />
        </LanguageProvider>
      </AuthProvider>
    </ThemeProvider>
  );
}

function DeepComponent() {
  const { theme } = useTheme();
  const { user } = useAuth();
  const { t } = useLanguage();

  return (
    <div class={`theme-${theme}`}>
      <h1>{t('welcome')}, {user.name}</h1>
    </div>
  );
}

4.2 分离 Context #

jsx
// 分离状态和操作
const ThemeStateContext = createContext(null);
const ThemeDispatchContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

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

  return (
    <ThemeStateContext.Provider value={state}>
      <ThemeDispatchContext.Provider value={dispatch}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeStateContext.Provider>
  );
}

function useThemeState() {
  return useContext(ThemeStateContext);
}

function useThemeDispatch() {
  return useContext(ThemeDispatchContext);
}

// 使用
function ThemeDisplay() {
  const { theme } = useThemeState();
  return <div>Current theme: {theme}</div>;
}

function ThemeToggle() {
  const { toggleTheme } = useThemeDispatch();
  return <button onClick={toggleTheme}>Toggle</button>;
}

五、性能优化 #

5.1 避免不必要的渲染 #

jsx
import { useMemo } from 'preact/hooks';

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  // 使用 useMemo 缓存 value
  const value = useMemo(() => ({
    theme,
    setTheme,
    toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light')
  }), [theme]);

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

5.2 拆分 Context #

jsx
// 将频繁变化和很少变化的状态分开
const UserStateContext = createContext(null);
const UserActionsContext = createContext(null);

function UserProvider({ children }) {
  const [user, setUser] = useState(null);

  // 用户状态(可能频繁变化)
  const state = useMemo(() => ({ user, isAuthenticated: !!user }), [user]);

  // 用户操作(很少变化)
  const actions = useMemo(() => ({
    login: async (credentials) => { /* ... */ },
    logout: () => setUser(null)
  }), []);

  return (
    <UserStateContext.Provider value={state}>
      <UserActionsContext.Provider value={actions}>
        {children}
      </UserActionsContext.Provider>
    </UserStateContext.Provider>
  );
}

六、Context vs Props #

6.1 何时使用 Context #

jsx
// ✅ 适合使用 Context
// - 全局主题
// - 用户信息
// - 语言设置
// - 应用配置

// ❌ 不适合使用 Context
// - 局部状态
// - 父子组件直接传递
// - 频繁变化的临时状态

6.2 对比 #

方面 Props Context
传递范围 逐层传递 跨层级
适用场景 局部数据 全局数据
调试 容易 较难
性能 可能触发大范围渲染

七、最佳实践 #

7.1 自定义 Hook 封装 #

jsx
// 封装 Context 使用
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

7.2 默认值设计 #

jsx
// 有意义的默认值
const ThemeContext = createContext({
  theme: 'light',
  setTheme: () => console.warn('No ThemeProvider found')
});

// 或使用 null 并在使用时检查
const ThemeContext = createContext(null);

7.3 Provider 组件命名 #

jsx
// 推荐:明确的功能命名
function ThemeProvider({ children }) { }
function AuthProvider({ children }) { }
function I18nProvider({ children }) { }

八、总结 #

要点 说明
createContext 创建 Context
Provider 提供数据
useContext 消费数据
useMemo 优化性能
自定义 Hook 封装使用

核心原则:

  • 用于全局共享数据
  • 使用 useMemo 优化
  • 封装自定义 Hook
  • 提供有意义的默认值
最后更新:2026-03-28