Типизация контекста
Создание контекста
import { createContext } from 'react';
type AuthContextValue = {
user: User | null;
login: (user: User) => void;
logout: () => void;
};
export const AuthContext = createContext<AuthContextValue | null>(null);
null — значение по умолчанию, если провайдера нет.
Провайдер
function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const value: AuthContextValue = {
user,
login: (u) => setUser(u),
logout: () => setUser(null),
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
Хук с проверкой
export function useAuth(): AuthContextValue {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
Возвращаемый тип — AuthContextValue, не AuthContextValue | null. Проверка сужает.
Использование
function Profile() {
const { user, logout } = useAuth();
if (!user) return <p>Войдите</p>;
return (
<>
<p>{user.name}</p>
<button onClick={logout}>Выйти</button>
</>
);
}
TypeScript знает, что logout — функция.
Значение по умолчанию без null
Если провайдер всегда есть — можно задать заглушку:
const AuthContext = createContext<AuthContextValue>({
user: null,
login: () => {},
logout: () => {},
});
Но тогда ошибка использования вне провайдера не проявится.
Generic-контекст
function createSafeContext<T>(name: string) {
const Context = createContext<T | null>(null);
function useSafeContext(): T {
const context = useContext(Context);
if (!context) {
throw new Error(`${name} must be used within provider`);
}
return context;
}
return [Context.Provider, useSafeContext] as const;
}
const [AuthProvider, useAuth] = createSafeContext<AuthContextValue>('Auth');
Разделение контекстов
Данные и действия — отдельные контексты:
const UserContext = createContext<User | null>(null);
const UserActionsContext = createContext<UserActions | null>(null);
Хуки:
function useUser(): User | null {
return useContext(UserContext);
}
function useUserActions(): UserActions {
const context = useContext(UserActionsContext);
if (!context) throw new Error('UserActionsContext is missing');
return context;
}
Значение с useMemo
const value = useMemo<AuthContextValue>(
() => ({ user, login, logout }),
[user, login, logout]
);
Контекст с reducer
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' };
const StateContext = createContext<State | null>(null);
const DispatchContext = createContext<Dispatch<Action> | null>(null);
function Provider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
Dispatch<Action> — встроенный тип React.
Итоги
createContext<T | null>(null)для проверки- Хук с проверкой возвращает
T - Generic-фабрика
createSafeContext - Разделяй данные и действия
Dispatch<Action>для reducer