TL;DR
Context API — это React-native решение для глобального state management без prop drilling. Разбираемся, когда его использовать, как избежать лишних ререндеров и какие есть альтернативы для сложных сценариев.
Введение: Context API в 2024
Несмотря на появление новых state management решений (Jotai, Zustand), Context API остается базовым инструментом в арсенале React-разработчика. Но многие senior-инженеры до сих пор используют его неправильно — либо как молоток для всех задач, либо избегают из-за мифов о производительности.
Основная часть: Beyond Basics
1. Оптимизация ререндеров
Главная проблема Context — все потребители (consumers) перерисовываются при изменении значения. Решение — разделение контекстов и мемоизация:
// Плохо: один контекст для всего
const AppContext = createContext({
user: null,
theme: 'light',
notifications: []
});
// Хорошо: split contexts
const UserContext = createContext(null);
const ThemeContext = createContext('light');
const NotificationsContext = createContext([]);
2. Паттерн “Context + useReducer”
Для сложной бизнес-логики вместо useState лучше использовать useReducer:
const CartContext = createContext(null);
const CartDispatchContext = createContext(null);
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
return (
<CartContext.Provider value={state}>
<CartDispatchContext.Provider value={dispatch}>
{children}
</CartDispatchContext.Provider>
</CartContext.Provider>
);
}
// Потребление с оптимизацией
function AddToCartButton({ productId }) {
const dispatch = useContext(CartDispatchContext);
return (
<button onClick={() => dispatch({ type: 'ADD', productId })}>
Add to Cart
</button>
);
}
3. TypeScript и Context
Продвинутая типизация для контекста с гарантией non-null значений:
type ThemeContextType = {
theme: 'light' | 'dark';
toggleTheme: () => void;
};
// Фабрика для безопасного контекста
function createStrictContext<T>() {
const context = createContext<T | null>(null);
const useStrictContext = () => {
const ctx = useContext(context);
if (!ctx) throw new Error('Context used without Provider');
return ctx;
};
return [context, useStrictContext] as const;
}
const [ThemeContext, useTheme] = createStrictContext<ThemeContextType>();
Практическое применение: Real-world примеры
1. Feature Flags
const FeatureFlagsContext = createContext<Record<string, boolean>>({});
export const FeatureFlagsProvider = ({
flags,
children,
}: {
flags: Record<string, boolean>;
children: ReactNode;
}) => {
const value = useMemo(() => flags, [flags]);
return (
<FeatureFlagsContext.Provider value={value}>
{children}
</FeatureFlagsContext.Provider>
);
};
// Потребление с кастомным хуком
export const useFeatureFlag = (flag: string) => {
const flags = useContext(FeatureFlagsContext);
return flags[flag] ?? false;
};
2. Performance-sensitive контексты
Для часто изменяющихся данных (например, положение курсора):
const MousePositionContext = createContext({ x: 0, y: 0 });
export const MousePositionProvider = ({ children }) => {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
// Оптимизация: стабильная ссылка на value
const value = useMemo(() => pos, [pos.x, pos.y]);
return (
<MousePositionContext.Provider value={value}>
{children}
</MousePositionContext.Provider>
);
};
// Оптимизированный consumer
function CursorTracker() {
const { x, y } = useContext(MousePositionContext);
return <div style={{ position: 'fixed', left: x, top: y }}>•</div>;
}
Когда НЕ использовать Context API
- Для высокочастотных обновлений (анимации, реальное время)
- Когда нужны селекторы (как в Redux)
- Для shared state между несвязанными частями приложения
- Когда нужны middleware (логирование, persistance)
Для этих случаев рассмотрите:
- Zustand/Jotai для глобального состояния
- useSyncExternalStore для внешних источников данных
- React Query для server state
Заключение
Context API — это не “упрощенный Redux”, а инструмент для композиции. Правильно используя разделение контекстов, мемоизацию и TypeScript, можно создавать производительные и поддерживаемые решения. Для сложных сценариев комбинируйте Context с другими state management подходами.
Источник: https://dev.to/m_saad_ahmad/day-9-of-100-days-of-code-understanding-the-react-context-api-2198