Перейти к содержимому

10. Context API

Context в Solid позволяет передавать данные вглубь дерева компонентов без «прокидывания пропсов». В отличие от React, контекст в Solid реактивен по умолчанию — достаточно положить в него сигнал.

import { createContext, useContext } from 'solid-js';
// Создаём контекст с типом и дефолтным значением
const ThemeContext = createContext({
theme: () => 'light' as 'light' | 'dark',
toggle: () => {},
});

Всегда указывай дефолтное значение — оно используется когда компонент находится вне 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>
);
}
// Прокидывание пропсов — подходит для 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>
);
}