diff --git a/lessons/lesson34/lesson.md b/lessons/lesson34/lesson.md index 6522f14..4ebfc46 100644 --- a/lessons/lesson34/lesson.md +++ b/lessons/lesson34/lesson.md @@ -1 +1,533 @@ -# Lesson 34 +--- +title: Занятие 34 +description: Разделение состояния между компонентами — подъем состояния, useContext и менеджеры состояния +--- + +# 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}

+ + + +
+ ); +} +``` + + + +Преимущества 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)