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 действий.