Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як типізувати контекст?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Найбезпечніший підхід - типізувати контекст як `T | undefined`, а доступ до нього віддати кастомному хуку, який кидає зрозумілу помилку, якщо провайдера немає: `createContext<T | undefined>(undefined)` + `useXxx()`. **Ключове:** давай контексту валідний дефолт лише тоді, коли він справді коректний для runtime, інакше це приховає баг.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1) Базовий і безпечний патерн (з `undefined` + кастомний хук) Коли контекст обов'язковий усередині піддерева і **не повинен** бути `null`/`undefined` під час використання. ```javascript import React, { createContext, useContext, ReactNode } from "react"; type Theme = "light" | "dark"; type ThemeCtx = { theme: Theme; setTheme: (t: Theme) => void; }; // 1) Контекст допускає undefined до Provider const ThemeContext = createContext<ThemeCtx | undefined>(undefined); // 2) Безпечний хук: кидає зрозумілу помилку, якщо провайдера немає export function useTheme() { const ctx = useContext(ThemeContext); if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>"); return ctx; } // 3) Провайдер із типами пропсів export function ThemeProvider({ value, children, }: { value: ThemeCtx; children: ReactNode; }) { return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; } ``` **Чому так краще:** - не потрібно писати `!`/кастувати `as ThemeCtx`; - отримуємо ранню і зрозумілу помилку, якщо забули обгорнути в провайдер. --- ## 2) Контекст із валідним дефолтом (якщо він реально є) Якщо поза провайдером значення **осмислене** (наприклад, read-only налаштування). ```javascript type Config = { apiBase: string; locale: string }; const defaultConfig: Config = { apiBase: "/api", locale: "en" }; const ConfigContext = createContext<Config>(defaultConfig); export const useConfig = () => useContext(ConfigContext); ``` **Важливо:** давай дефолт **тільки тоді**, коли він справді коректний для runtime, інакше ти приховаєш баг. --- ## 3) Контекст, який може бути `null` за доменом Якщо «немає значення» - це **нормальний** кейс. ```javascript type User = { id: string; name: string } | null; const CurrentUserContext = createContext<User>(null); export const useCurrentUser = () => useContext(CurrentUserContext); ``` --- ## 4) Пара «state + dispatch» (зручно для редьюсерів) ```javascript type Action = | { type: "set"; payload: number } | { type: "reset" }; type CounterState = { count: number }; type CounterCtx = { state: CounterState; dispatch: React.Dispatch<Action>; }; const CounterContext = createContext<CounterCtx | undefined>(undefined); export function useCounter() { const ctx = useContext(CounterContext); if (!ctx) throw new Error("useCounter must be used within <CounterProvider>"); return ctx; } ``` --- ## 5) Кортеж/мульти-значення (іноді зручніше) ```javascript type Filters = { q: string; tags: string[] }; type SetFilters = (next: Filters) => void; const FiltersContext = createContext<[Filters, SetFilters] | undefined>(undefined); export function useFilters() { const ctx = useContext(FiltersContext); if (!ctx) throw new Error("useFilters must be used within <FiltersProvider>"); return ctx; // поверне [filters, setFilters] } ``` --- ## 6) Типізація провайдера (пропси `value` і `children`) ```javascript type AuthCtx = { token: string | null; login: (t: string) => void }; const AuthContext = createContext<AuthCtx | undefined>(undefined); type AuthProviderProps = { value: AuthCtx; // строго типізуємо value children: ReactNode; }; export function AuthProvider({ value, children }: AuthProviderProps) { return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } ``` --- ## 7) Уникай анти-патернів - `createContext<Ctx>(null as unknown as Ctx)` чи `null!` - приховує помилки. - Давати «фіктивний» дефолт, якщо без провайдера працювати **не можна**. - Зберігати в контексті часто змінювані примітиви без потреби (спричинить зайві рендери). --- ## 8) Нюанси і поради - Для великих значень мемоізуй `value` у провайдері: ```javascript const value = useMemo(() => ({ theme, setTheme }), [theme]); ``` - Для SSR/тестів зручно мати **валідний дефолт** (патерн №2) - якщо це справді безпечно. - Якщо контекст використовується в різних пакетах, експортуй **і контекст, і хук**, але споживачам віддавай саме хук - так ти нав'яжеш безпечне використання. --- ### Коротка шпаргалка - **Обов'язковий провайдер** -> `createContext<T | undefined>(undefined)` + `useXxx()` з помилкою. - **Є осмислений дефолт** -> `createContext<T>(defaultValue)`. - **Допускається відсутність** -> `createContext<T | null>(null)`. - **Reducer-стиль** -> `createContext<{ state: S; dispatch: Dispatch<A> } | undefined>(undefined)`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.