Урок 07

useContext: доступ к контексту

useContext читает значение из React-контекста. Контекст позволяет передавать данные через дерево компонентов без явной передачи через пропсы.

Проблема

Если данные нужны глубоко в дереве, их приходится прокидывать через все промежуточные компоненты:

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

Это называется prop drilling. Контекст решает эту проблему.

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

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);

Провайдер

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

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>
    );
}

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

<AuthProvider>
    <App />
</AuthProvider>

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

import { useContext } from 'react';

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

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

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

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

Часто контекст оборачивают в хук с проверкой:

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

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

    return context;
}

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

function Profile() {
    const { user } = useAuth();

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

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

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

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

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

Ре-рендеры

Когда значение контекста меняется — все потребители перерисовываются. Если в контексте объект, создаваемый в провайдере, он меняется при каждом рендере и вызывает перерисовку всех потребителей.

Решение — мемоизация:

const value = useMemo(
    () => ({ user, login, logout }),
    [user]
);

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

Несколько контекстов

Можно вкладывать:

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

Когда использовать

  • Тема (светлая/тёмная)
  • Локаль (язык)
  • Данные пользователя
  • Настройки приложения
  • Корзина покупок

Не для всего: если данные нужны только на одном уровне — передавай пропсы.

Итоги

  • useContext читает значение контекста
  • Контекст решает проблему prop drilling
  • Провайдер оборачивает дерево
  • Значение мемоизируй через useMemo
  • Кастомный хук с проверкой удобнее
  • Не для всех данных — только для общих