Урок 04

useContext: базово

Context позволяет передавать данные через дерево компонентов, не прокидывая их через пропсы на каждом уровне.

Проблема prop drilling

<App user={user}>
    <Layout user={user}>
        <Sidebar user={user}>
            <Profile user={user} />

Layout и Sidebar не используют user, но вынуждены его передавать. Чем глубже дерево — тем хуже.

Создание контекста

import { createContext } from 'react';

type User = {
    id: number;
    name: string;
};

type AuthContextValue = {
    user: User | null;
    login: (user: User) => void;
    logout: () => void;
};

export const AuthContext = createContext<AuthContextValue | null>(null);

null — значение по умолчанию, если провайдера нет.

Провайдер

Провайдер предоставляет значение потомкам:

function AuthProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState<User | null>(null);

    const login = (user: User) => setUser(user);
    const logout = () => setUser(null);

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

Оборачивание приложения

function App() {
    return (
        <AuthProvider>
            <Main />
        </AuthProvider>
    );
}

Использование

import { useContext } from 'react';

function Profile() {
    const auth = useContext(AuthContext);

    if (!auth?.user) return <p>Войдите</p>;

    return <p>{auth.user.name}</p>;
}

Profile получает данные напрямую, без пропсов.

Значение по умолчанию

Если провайдера нет — вернётся значение из createContext:

const ThemeContext = createContext<'light' | 'dark'>('light');

Вложенные провайдеры

<AuthProvider>
    <ThemeProvider>
        <App />
    </ThemeProvider>
</AuthProvider>

Внутренние компоненты видят оба контекста.

Обновление значения

Когда значение контекста меняется — все потребители перерисовываются. Это нормально, если данные действительно общие.

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

    const toggle = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'));

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

Кастомный хук

Оборачивай useContext в хук с проверкой:

export function useAuth() {
    const context = useContext(AuthContext);

    if (!context) {
        throw new Error('useAuth must be used within AuthProvider');
    }

    return context;
}

Использование:

const { user, login, logout } = useAuth();

Итоги

  • Контекст решает prop drilling
  • createContext создаёт контекст
  • Провайдер предоставляет значение
  • useContext читает значение
  • Кастомный хук с проверкой — стандарт
  • Обновление значения вызывает ререндер потребителей