10. Context API
Context в Solid позволяет передавать данные вглубь дерева компонентов без «прокидывания пропсов». В отличие от React, контекст в Solid реактивен по умолчанию — достаточно положить в него сигнал.
createContext — создание контекста
Заголовок раздела «createContext — создание контекста»import { createContext, useContext } from 'solid-js';
// Создаём контекст с типом и дефолтным значениемconst ThemeContext = createContext({ theme: () => 'light' as 'light' | 'dark', toggle: () => {},});Всегда указывай дефолтное значение — оно используется когда компонент находится вне Provider’а.
Provider — предоставление значения
Заголовок раздела «Provider — предоставление значения»import { createSignal, createContext, useContext } from 'solid-js';import type { ParentComponent } from 'solid-js';
// Шаблон: тип + контекст + провайдер + хукinterface ThemeContextValue { theme: Accessor<'light' | 'dark'>; toggle: () => void; isDark: Accessor<boolean>;}
const ThemeContext = createContext<ThemeContextValue>();
export const ThemeProvider: ParentComponent = (props) => { const [theme, setTheme] = createSignal<'light' | 'dark'>('light'); const toggle = () => setTheme(t => t === 'light' ? 'dark' : 'light'); const isDark = createMemo(() => theme() === 'dark');
return ( <ThemeContext.Provider value={{ theme, toggle, isDark }}> {props.children} </ThemeContext.Provider> );};
// Кастомный хук для использования контекстаexport function useTheme() { const ctx = useContext(ThemeContext); if (!ctx) throw new Error('useTheme: ThemeProvider не найден в дереве'); return ctx;}Реактивность контекста через сигналы
Заголовок раздела «Реактивность контекста через сигналы»Главное отличие от React: в Solid не нужен useReducer или специальные паттерны. Просто положи сигнал в контекст:
const CounterContext = createContext<{ count: Accessor<number>; increment: () => void; decrement: () => void;}>();
function CounterProvider(props: { children: any; initial?: number }) { const [count, setCount] = createSignal(props.initial ?? 0);
// props.initial реактивен — если родитель изменит его, контекст обновится return ( <CounterContext.Provider value={{ count, increment: () => setCount(c => c + 1), decrement: () => setCount(c => c - 1), }}> {props.children} </CounterContext.Provider> );}Множественные контексты
Заголовок раздела «Множественные контексты»// Каждый контекст независимfunction App() { return ( <ThemeProvider> <AuthProvider> <LocaleProvider locale="ru"> <Router> <MainLayout /> </Router> </LocaleProvider> </AuthProvider> </ThemeProvider> );}
// Использование в глубоко вложенных компонентахfunction DeepButton() { const { isDark } = useTheme(); const { user } = useAuth(); const { t } = useLocale();
return ( <button class={isDark() ? 'btn-dark' : 'btn-light'}> {t('greet', { name: user()?.name })} </button> );}Контекст vs пропсы
Заголовок раздела «Контекст vs пропсы»// Прокидывание пропсов — подходит для 1-2 уровнейfunction App() { const [theme, setTheme] = createSignal('light'); return <Layout theme={theme} setTheme={setTheme} />;}function Layout(props: { theme: Accessor<string>; setTheme: Setter<string> }) { return <Header theme={props.theme} setTheme={props.setTheme} />;}// ^ Каждый промежуточный компонент вынужден знать о theme
// Context — для «глобальных» данных: тема, авторизация, локальfunction App() { return <ThemeProvider><Layout /></ThemeProvider>;}function Layout() { return <Header />; // Не знает о теме}function Header() { const { theme } = useTheme(); // Берёт напрямую из контекста return <div class={theme()}>...</div>;}Паттерн: контекст с действиями
Заголовок раздела «Паттерн: контекст с действиями»interface AuthContextValue { user: Accessor<User | null>; isAuthenticated: Accessor<boolean>; login: (credentials: Credentials) => Promise<void>; logout: () => void;}
const AuthContext = createContext<AuthContextValue>();
function AuthProvider(props: { children: any }) { const [user, setUser] = createSignal<User | null>(null); const isAuthenticated = createMemo(() => user() !== null);
const login = async (credentials: Credentials) => { const response = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify(credentials), }); const data = await response.json(); setUser(data.user); };
const logout = () => { setUser(null); // Очистить localStorage, cookies и т.д. };
return ( <AuthContext.Provider value={{ user, isAuthenticated, login, logout }}> {props.children} </AuthContext.Provider> );}Гнездование провайдеров одного типа
Заголовок раздела «Гнездование провайдеров одного типа»// Провайдеры можно вкладывать — внутренний перекрывает внешнийfunction App() { return ( <ThemeProvider theme="light"> <div>Светлая тема</div> <ThemeProvider theme="dark"> <div>Тёмная тема (перекрыта)</div> </ThemeProvider> </ThemeProvider> );}