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