Состояние и редьюсеры; типизация Context
Управляйте состоянием компонента с помощью useState и useReducer, а затем выносите состояние в типизированный Context с безопасным пользовательским хуком.
«Состояние и редьюсеры; типизация Context» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 2 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 3 уроков всего.
Введение
Цель: начните с локального состояния (useState), перейдите к типизированному редуктору (useReducer), а затем поднимите состояние в типизированный контекст с безопасным пользовательским хуком.
- Явно моделируйте состояние и действия
- Предпочитайте размеченные объединения
- Защищайте доступ к контексту
Типизированный useState
Аннотируйте состояние конкретным типом (например, number). Используйте форму обновления, чтобы избежать устаревших замыканий при быстрых обновлениях.
import React, { useState } from "react"
export default function Counter() {
const [count, setCount] = useState<number>(0)
const inc = () => setCount(c => c + 1)
const dec = () => setCount(c => c - 1)
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {count}</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={inc}>+1</button>
<button onClick={dec}>-1</button>
</div>
</div>
)
}Типизированный useReducer
Моделируйте действие как размеченное объединение. Конструкция switch остаётся исчерпывающей, а полезные данные типизируются для каждой ветки.
import React, { useReducer } from "react"
type State = { count: number }
type Action =
| { type: "inc" }
| { type: "add"; by: number }
| { type: "reset" }
function reducer(s: State, a: Action): State {
switch (a.type) {
case "inc": return { count: s.count + 1 }
case "add": return { count: s.count + a.by }
case "reset": return { count: 0 }
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {state.count}</div>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
<button onClick={() => dispatch({ type: "add", by: 5 })}>+5</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
)
}Контекст и хук
Создайте Context<T | null>, а затем предоставьте хук useX, который выдаёт ошибку при значении null. Код потребителей остаётся простым и хорошо типизированным.
import React, { createContext, useContext, useReducer } from "react"
type State = { count: number }
type Action = { type: "inc" } | { type: "add"; by: number } | { type: "reset" }
function reducer(s: State, a: Action): State {
switch (a.type) {
case "inc": return { count: s.count + 1 }
case "add": return { count: s.count + a.by }
case "reset": return { count: 0 }
}
}
type Ctx = { state: State; dispatch: React.Dispatch<Action> }
const CounterCtx = createContext<Ctx | null>(null)
export function useCounter() {
const ctx = useContext(CounterCtx)
if (!ctx) throw new Error("useCounter must be used within CounterProvider")
return ctx
}
export function CounterProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return <CounterCtx.Provider value={{ state, dispatch }}>{children}</CounterCtx.Provider>
}
export default function App() {
return (
<CounterProvider>
<Inner />
</CounterProvider>
)
}
function Inner() {
const { state, dispatch } = useCounter()
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {state.count}</div>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
</div>
)
}Контекст и шаблон с редуктором
Разделите чтение и отправку действий между компонентами. Провайдер оборачивает дочерние элементы и предоставляет типизированные состояние и отправку действий.
import React from "react"
import { CounterProvider, useCounter } from "./ctx"
function Controls() {
const { dispatch } = useCounter()
return (
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
<button onClick={() => dispatch({ type: "add", by: 10 })}>+10</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
)
}
function View() {
const { state } = useCounter()
return <div>Count: {state.count}</div>
}
export default function App() {
return (
<CounterProvider>
<div style={{ display: "grid", gap: 8 }}>
<View />
<Controls />
</div>
</CounterProvider>
)
}Лучшие практики
Рекомендации:
- Делайте состояние минимальным, а остальное выводите из него.
- Предпочитайте размеченные объединения для действий.
- Защищайте доступ к контексту с помощью пользовательского хука.
Проверка типизации контекста
Быстрая проверка: как безопасно типизировать контекст React?
Повторение
Повторение: начните с useState, моделируйте переходы с помощью useReducer и поднимите общее состояние в типизированный контекст, защищённый пользовательским хуком.
Часто задаваемые вопросы
Урок «Состояние и редьюсеры; типизация Context» бесплатный?
Да — полный текст урока «Состояние и редьюсеры; типизация Context» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 3 уроков всего.
Чему я научусь в уроке «Состояние и редьюсеры; типизация Context»?
Управляйте состоянием компонента с помощью useState и useReducer, а затем выносите состояние в типизированный Context с безопасным пользовательским хуком. Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 3.
Сколько времени занимает урок «Состояние и редьюсеры; типизация Context»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Свойства и дочерние элементы; обработчики событий; ссылки
- Состояние и редьюсеры; типизация Context
- Обобщения компонентов — введение