React: шпаргалка — хуки, React 19, Next.js, Zustand, TypeScript, формы
React — самая популярная библиотека для создания пользовательских интерфейсов. Шпаргалка покрывает все хуки, React 19, Next.js, стейт-менеджмент и решения реальных проблем.
Компоненты и JSX
// Функциональный компонент
function Greeting({ name }) { return <h1>Привет, {name}!</h1> }
const Greeting = ({ name }) => <h1>Привет, {name}!</h1>
// Выражения, условия, списки
const el = <h1>Привет, {name}!</h1>
{isLoggedIn && <Dashboard />}
{isLoggedIn ? <Dashboard /> : <Login />}
{users.map(u => <UserCard key={u.id} user={u} />)} // key = стабильный ID!
// Стили
<div className="card active" style={{ marginTop: 8 }} />
Хуки: полный справочник
useState — состояние
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: '', age: 0 })
setCount(42) // новое значение
setCount(prev => prev + 1) // функциональная форма (надёжнее!)
setUser({ ...user, name: 'Иван' }) // ❌ user.name = 'Иван' — мутация не сработает
setItems([...items, newItem]) // ❌ items.push() — мутация не сработает
const [data, setData] = useState(() => expensiveComputation()) // lazy init
useEffect — эффекты
// Монтирование
useEffect(() => { fetchData().then(setData) }, [])
// Слежение за зависимостью
useEffect(() => { fetchUser(id).then(setUser) }, [id])
// Очистка (таймеры, подписки, fetch-отмена)
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal }).then(...)
const timer = setInterval(tick, 1000)
return () => { controller.abort(); clearInterval(timer) }
}, [url])
useRef — ссылка без ререндера
const inputRef = useRef(null)
<input ref={inputRef} />
inputRef.current.focus()
// Хранение предыдущего значения
const prevValue = useRef(value)
useEffect(() => { prevValue.current = value }, [value])
// Счётчик без ререндера (НЕ для UI!)
countRef.current += 1
useContext — глобальное состояние
const ThemeContext = createContext('light')
function App() {
return <ThemeContext.Provider value="dark"><Page /></ThemeContext.Provider>
}
function Button() {
const theme = useContext(ThemeContext)
return <button className={theme}>Кнопка</button>
}
useMemo / useCallback — мемоизация
const sorted = useMemo(() => items.sort((a, b) => a.price - b.price), [items])
const handleClick = useCallback((id) => { setSelected(id) }, [])
// ❌ Не мемоизировать без профилирования! «На всякий случай» — вредно
useReducer — сложная логика
const reducer = (state, action) => {
switch (action.type) {
case 'increment': return { count: state.count + 1 }
case 'reset': return { count: 0 }
default: return state
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 })
Кастомные хуки
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const ctrl = new AbortController()
setLoading(true)
fetch(url, { signal: ctrl.signal }).then(r => r.json()).then(setData)
.catch(e => e.name !== 'AbortError' && setError(e))
.finally(() => setLoading(false))
return () => ctrl.abort()
}, [url])
return { data, loading }
}
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value)
useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t) }, [value, delay])
return debounced
}
React 19: новые хуки
// useOptimistic — мгновенное обновление UI с откатом при ошибке
function TodoList() {
const [todos, setTodos] = useState([])
const [optimisticTodos, addOptimistic] = useOptimistic(todos,
(state, newTodo) => [...state, { text: newTodo, pending: true }]
)
async function addTodo(formData) {
addOptimistic(formData.get('text'))
await saveToServer(formData)
setTodos(prev => [...prev, { text: formData.get('text') }])
}
}
// useActionState — серверный экшен с состоянием (замена useFormState)
function ChangeName() {
const [state, submitAction, isPending] = useActionState(updateName, { error: null })
return (
<form action={submitAction}>
<input name="name" />
<button disabled={isPending}>Сохранить</button>
{state?.error && <p className="error">{state.error}</p>}
</form>
)
}
// useFormStatus — статус родительской формы
function SubmitButton() {
const { pending, data, method } = useFormStatus()
return <button disabled={pending}>{pending ? 'Сохранение...' : 'Сохранить'}</button>
}
React 19: Server Components (RSC)
// Серверный компонент (по умолчанию в Next.js App Router)
async function UserList() {
const users = await db.query('SELECT * FROM users') // прямой доступ к БД
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
// Клиентский компонент
'use client'
function LikeButton() {
const [liked, setLiked] = useState(false)
return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>
}
Next.js App Router
// app/layout.tsx — корневой layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html><body><Header />{children}<Footer /></body></html>
}
// app/users/page.tsx — страница с загрузкой данных
export default async function UsersPage() {
const users = await fetch('https://api.example.com/users').then(r => r.json())
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
// app/users/[id]/page.tsx — динамическая страница
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await fetch(`https://api.example.com/users/${id}`).then(r => r.json())
return <h1>{user.name}</h1>
}
// Навигация
import Link from 'next/link'
import { useRouter } from 'next/navigation'
<Link href="/users">Пользователи</Link>
const router = useRouter(); router.push('/users')
Zustand — лёгкий стейт-менеджмент
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
fetchData: async (id) => {
const data = await api.fetch(id)
set({ data })
},
}))
function Counter() {
const count = useStore((s) => s.count) // селектор — только на count!
const increment = useStore((s) => s.increment)
return <button onClick={increment}>{count}</button>
}
React Hook Form — формы без боли
import { useForm } from 'react-hook-form'
function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm()
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('email', { required: 'Email обязателен', pattern: { value: /^\S+@\S+$/i, message: 'Неверный email' } })} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('password', { required: true, minLength: 8 })} />
<button type="submit">Отправить</button>
</form>
)
}
TypeScript с React
// Пропсы
interface Props {
name: string
age?: number
children: React.ReactNode
onClick?: () => void
}
function UserCard({ name, age, children, onClick }: Props) { return <div onClick={onClick}>{name} — {age}{children}</div> }
// useState с типами
const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle')
// useRef: readonly vs mutable
const inputRef = useRef<HTMLInputElement>(null) // readonly
const timerRef = useRef<number | null>(null) // mutable (нет readonly)
// Event handlers
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault() }
// Children types
type Props = { children: React.ReactNode } // любой JSX
type CardProps = { renderHeader: () => React.ReactNode } // render prop
React Router v7
import { BrowserRouter, Routes, Route, Link, useParams, useNavigate, Outlet } from 'react-router-dom'
// Layout с <Outlet>
function DashboardLayout() {
return <div><Sidebar /><Outlet /></div>
}
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="users/:id" element={<UserDetail />} />
</Route>
</Routes>
Производительность
const Expensive = React.memo(({ data }: { data: Item[] }) => <HeavyRendering data={data} />)
const LazyPage = React.lazy(() => import('./HeavyPage'))
<Suspense fallback={<Spinner />}><LazyPage /></Suspense>
const [isPending, startTransition] = useTransition()
startTransition(() => setSearchQuery(value))
Правила хуков
- Вызывать только на верхнем уровне — не внутри условий, циклов
- Только из React-компонентов или кастомных хуков
- Кастомные хуки начинать с
use
// ❌ Нарушение
if (condition) { useEffect(() => {}) }
// ✅ Правильно
useEffect(() => { if (condition) {} }, [condition])
Частые проблемы и их решения
| Проблема | ❌ Ошибка | ✅ Решение |
|---|---|---|
| Бесконечный useEffect | useEffect(() => { setCount(count + 1) }, [count]) |
useEffect(() => { setCount(prev => prev + 1) }, []) |
| Замыкание старого состояния | setTimeout(() => console.log(count), 3000) |
const ref = useRef(count); ref.current = count |
| Мутация состояния | items.push(x); setItems(items) |
setItems([...items, x]) |
| Контекст ререндерит всё дерево | Один контекст на всё приложение | Разделить на мелкие контексты |
| Утечка без cleanup | useEffect(() => { subscribe() }) |
useEffect(() => { const u = subscribe(); return u }, []) |
| useCallback без смысла | Каждая функция в useCallback | Только при передаче в React.memo |
| key по индексу | items.map((_, i) => <li key={i}> |
items.map(i => <li key={i.id}> |
| setState после unmount | Асинхронный setState без проверки | useEffect(() => { let ok = true; fetch().then(d => ok && setData(d)); return () => { ok = false } }, []) |
Полезные ресурсы
- React документация — официальная документация
- React TypeScript Cheatsheet — типизация
- Next.js документация — фреймворк
- Zustand — стейт-менеджмент
- React Hook Form — формы
Что дальше
- Шпаргалка по Vue 3 — сравнение React и Vue
- Шпаргалка по Svelte 5 — альтернатива с компилятором
- Шпаргалка по TypeScript — типизация компонентов
- Шпаргалка по Docker — контейнеризация React-приложений
Связанные шпаргалки: Vue 3, Svelte 5, TypeScript, Next.js, Docker