webroad.online
  1. 1Веб
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Инструменты
  8. 8React
  9. 9Стейт-менеджмент
  10. 10Next.js
  11. 11Формы
  12. 12Данные и бэкенд
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Анимации
  16. 16Тестирование
  17. 17Архитектура
TypeScript · Урок 6 из 6

TypeScript в React и Next

Props, children, состояние, события, типизированный reducer и PageProps.

Обновлено

Что значит типизировать React

В React TS проверяет три главные вещи: props, которые получает компонент, состояние и события. Результат: автодополнение для props, ошибки, если забыли обязательный, рефакторинг без страха.

Props

type ButtonProps = {
  variant?: 'primary' | 'secondary'
  size?: 'sm' | 'md'
  children: React.ReactNode
  onClick?: () => void
}

export function Button({ variant = 'primary', size = 'md', children, onClick }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>
}

Расширяем props нативного элемента

Вместо того чтобы перечислять type, disabled, aria-*... вручную:

type Props = React.ComponentProps<'button'> & { variant?: 'primary' | 'ghost' }

export function Button({ variant = 'primary', className, ...props }: Props) {
  return <button className={cn(variants[variant], className)} {...props} />
}

Именно так написан shared/ui/button.tsx в этом проекте.

Типы для children — различия

Тип Принимает Когда
React.ReactNode всё, что можно отрисовать: JSX, строку, число, null, массив по умолчанию для children
React.ReactElement только JSX-элемент когда действительно нужен элемент (редко)
string только текст простые подписи

Состояние

const [count, setCount] = useState(0)                   // вывод: number
const [user, setUser] = useState<User | null>(null)     // явный дженерик, когда начальное значение null
const [status, setStatus] = useState<'idle' | 'loading'>('idle')

События

function onChange(e: React.ChangeEvent<HTMLInputElement>) {
  setQuery(e.target.value)
}

function onSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault()
  const data = new FormData(e.currentTarget)
}

Трюк: напишите обработчик прямо в JSX (onChange={e => ...}) — TS выведет тип; наведите на e и скопируйте его.

Reducer с discriminated union

type Action =
  | { type: 'add'; text: string }
  | { type: 'toggle'; id: number }

function reducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case 'add': return [...state, { id: Date.now(), text: action.text, done: false }]
    case 'toggle': return state.map(t => (t.id === action.id ? { ...t, done: !t.done } : t))
  }
}

dispatch({ type: 'add' }) без text → ошибка. См. Union types.

Особенности Next.js

export default async function Page({ params }: PageProps<'/blog/[slug]'>) {
  const { slug } = await params        // типизировано автоматически по структуре папок
}

export default function Layout({ children }: LayoutProps<'/'>) { ... }

PageProps и LayoutProps Next генерирует по вашим маршрутам.

Коротко

  • Props как type; ComponentProps<'button'> для расширения нативных элементов.
  • children: React.ReactNode; useState<T>, когда начальное значение говорит не всё.
  • События: ChangeEvent, FormEvent; reducer с union действий.

Официальные источники

Упражнения

Была ли страница полезной?

Один клик — без регистрации.