# OTUS ## JavaScript Basic ### Вопросы? ### Разделение состояния между компонентами ### подъем состояния ### useContext ### State managers ### Проблема как разделить состояние между компонентами? React-компоненты часто должны **делиться данными** между собой. Пример: у нас есть компонент формы и компонент отображения результата. ```jsx function InputForm({ onSubmit }) { const [value, setValue] = useState(""); return (
{ e.preventDefault(); onSubmit(value); }} >
setValue(e.target.value)} />
Отправить
); } function Result({ text }) { return
Результат: {text}
; } ``` Если оба компонента независимы — где хранить общее состояние? Подъем состояния (Lifting State Up) Это первый способ "поделиться" данными: перенести состояние выше по иерархии. ```jsx function App() { const [text, setText] = useState(""); return (
); } ``` Плюсы: - Просто и понятно - Отлично подходит для небольших приложений - Без сторонних зависимостей Минусы: - Чем больше компонентов, тем сложнее передавать пропсы вниз - "Пробрасывание пропсов" (props drilling) Props Drilling ```jsx
``` Props Drilling ```mermaid graph TD A["App"] -->|props: user| B["Header"] A -->|props: user| C["Main"] C -->|props: user| D["Sidebar"] D -->|props: user| E["Profile (использует user)"] %% Узлы style A fill:#f9f,stroke:#333,stroke-width:1px,color:#000 style B fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style C fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style D fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style E fill:#bbf,stroke:#333,stroke-width:1px,color:#000 %% Линии linkStyle 0 stroke:#999,stroke-width:2px,color:#333 linkStyle 1 stroke:#999,stroke-width:2px,color:#333 linkStyle 2 stroke:#999,stroke-width:2px,color:#333 linkStyle 3 stroke:#00f,stroke-width:2.5px,color:#00f ``` Приходится передавать **user** через все уровни, даже если промежуточным он не нужен. Решение: Контекст (Context API) React Context позволяет избежать props drilling и делиться состоянием напрямую между компонентами. ```jsx const UserContext = createContext(); function App() { const [user, setUser] = useState({ name: "Andrey" }); return (
); } function Header() { const user = useContext(UserContext); return
Привет, {user.name}!
; } ``` Визуализация Context API ```mermaid graph TD %% Компоненты A["App (UserContext.Provider)"] --> B["Header"] A --> C["Main"] C --> D["Sidebar"] D --> E["Profile (useContext)"] %% Прямая линия контекста A -.->|context: user| E %% Стили узлов style A fill:#bbf,stroke:#333,stroke-width:1px,color:#000 style B fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style C fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style D fill:#eee,stroke:#aaa,stroke-width:1px,color:#333 style E fill:#f9f,stroke:#333,stroke-width:1px,color:#000 %% Стили линий linkStyle 0 stroke:#999,stroke-width:1.5px linkStyle 1 stroke:#999,stroke-width:1.5px linkStyle 2 stroke:#999,stroke-width:1.5px linkStyle 3 stroke:#999,stroke-width:1.5px linkStyle 4 stroke:#00f,stroke-width:2.5px,stroke-dasharray:5 5 ``` Плюсы: - Удобно для "глобальных" данных (тема, язык, текущий пользователь) - Нет необходимости передавать пропсы вручную Минусы: - Контексты плохо подходят для часто изменяющегося состояния - Труднее тестировать и дебажить - Перерисовываются все потребители при изменении контекста Когда использовать Context - Данные нужны многим компонентам на разных уровнях - Эти данные редко меняются (напр. настройки, тема) - Вы хотите избежать пропс-дриллинга Контексты можно комбинировать ```jsx
``` И читать их отдельно ```jsx const theme = useContext(ThemeContext); const user = useContext(UserContext); ``` ### Вопросы? ### Менеджеры состояния (State Managers) Когда нужен State Manager? В небольших приложениях достаточно: - Подъёма состояния (Lifting State Up) - Context API для глобальных, редко меняющихся данных Когда приложение растёт, context и поднятие состояния перестают справляться. Типичные проблемы: - Много компонентов используют одни и те же данные - Пропсы приходится передавать через несколько уровней - Контексты становятся сложными и их трудно отлаживать - Частые перерисовки компонентов → падение производительности Здесь появляются менеджеры состояния (state management libraries): - Redux - Zustand - Recoil - Jotai - MobX State Manager (Redux, Zustand, Recoil) — это: - Центральный источник данных (Store) - Компоненты подписаны на изменения - Данные меняются через события / actions - Уведомления через event bus, а не пропсы Идея: **разделение состояния и UI**, чтобы компоненты не зависели друг от друга напрямую. ```mermaid graph TD %% Компоненты A["Component A"] -->|action| S["State Manager / Store"] B["Component B"] -->|action| S C["Component C"] -->|action| S %% State manager уведомляет компоненты S -->|update| A S -->|update| B S -->|update| C %% Стили узлов style A fill:#bbf,stroke:#333,stroke-width:1px,color:#000 style B fill:#bbf,stroke:#333,stroke-width:1px,color:#000 style C fill:#bbf,stroke:#333,stroke-width:1px,color:#000 style S fill:#f9f,stroke:#333,stroke-width:2px,color:#000 %% Стили линий linkStyle 0 stroke:#00f,stroke-width:2px linkStyle 1 stroke:#00f,stroke-width:2px linkStyle 2 stroke:#00f,stroke-width:2px linkStyle 3 stroke:#0a0,stroke-width:2px,stroke-dasharray:5 5 linkStyle 4 stroke:#0a0,stroke-width:2px,stroke-dasharray:5 5 linkStyle 5 stroke:#0a0,stroke-width:2px,stroke-dasharray:5 5 ``` Зачем нужны менеджеры состояния? - Централизуют все данные приложения - Обеспечивают предсказуемость - Позволяют легко обновлять и отслеживать изменения - Улучшают производительность ### Redux Toolkit Когда стоит переходить на Redux Toolkit? Context API отлично подходит для небольших и средних приложений, но при росте сложности появляются проблемы: - Слишком много контекстов - Перерисовки при изменении состояния - Неудобно отслеживать логику обновления - Трудно дебажить Что такое Redux Toolkit? Redux Toolkit (RTK) — официальный, **рекомендуемый способ** работы с Redux. Он решает старые проблемы Redux: - Меньше шаблонного кода - Простая настройка - Интеграция с TypeScript и React - Поддержка `immer` (иммутабельность под капотом) Установка ```bash npm install @reduxjs/toolkit react-redux ``` Базовая структура ``` src/ ├─ app/ │ └─ store.js ├─ features/ │ └─ counter/ │ ├─ counterSlice.js │ └─ Counter.jsx └─ App.jsx ``` Пояснение: - **Store** — где хранится всё состояние - **Slice** — часть состояния + actions - **Компонент** — подписывается на данные и диспатчит actions ```jsx import { createSlice } from "@reduxjs/toolkit"; const counterSlice = createSlice({ name: "counter", initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, reset: (state) => { state.value = 0; }, }, }); export const { increment, decrement, reset } = counterSlice.actions; export default counterSlice.reducer; ``` Настройка store ```jsx import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "../features/counter/counterSlice"; export const store = configureStore({ reducer: { counter: counterReducer, }, }); ``` Подключение к React ```jsx import React from "react"; import ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; import { store } from "./app/store"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root")).render(
); ``` Использование в компоненте ```jsx import { useSelector, useDispatch } from "react-redux"; import { increment, decrement, reset } from "./counterSlice"; export function Counter() { const value = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return (
Счётчик: {value}
dispatch(increment())}>+
dispatch(decrement())}>-
dispatch(reset())}>Сброс
); } ``` Преимущества Redux Toolkit - Минимум шаблонного кода - Простая типизация - Отличная интеграция с DevTools - Легко масштабируется - Подходит для крупных приложений Когда выбирать Redux Toolkit - Когда приложение растёт - Когда нужно предсказуемое состояние - Когда важен контроль и отладка - Когда Context API становится неудобным ### Вопросы? ### Что важно запомнить - Всё — **состояние**, вопрос лишь _где оно живёт_ - **Начинай просто** — поднимай состояние, используй контекст - Когда становится сложно — переходи к **Redux Toolkit** - Redux теперь не "монстр", а удобный инструмент с RTK ### Итого - React даёт гибкость, но ответственность за архитектуру на вас - Управление состоянием — ключевой навык фронтенд-разработчика - Redux Toolkit — современный, простой и безопасный способ масштабировать приложение - Важно понимать не только "как", но и "зачем" ### Дополнительные материалы 1. [Написание тестов для связки React + Redux](https://redux.js.org/usage/writing-tests#connected-components) 1. [Мини-курс по Redux от Дэна Абрамова](https://egghead.io/) 1. [Продвинутое продолжение курса (по связке React + Redux)](https://egghead.io/courses/building-react-applications-with-idiomatic-redux) 1. [React HoC в TypeScript. Типизация без боли](https://habr.com/ru/company/sberbank/blog/354104/) 1. [React TypeScript Cheat sheet: Full HOC Example](https://react-typescript-cheatsheet.netlify.app/docs/hoc/full_example)