Irbisa · Шпаргалка
TypeScript в React
Типизация props, children и событий, дженерик-компоненты, discriminated unions, утилитные типы, типизация хуков и контекста, strict
Как типизировать пропсы компонента и нужен ли для этого
React.FC?interface ButtonProps { label: string; variant?: 'primary' | 'secondary'; disabled?: boolean; onClick: () => void;}…Как типизировать
childrenи чем отличаютсяReactNode,ReactElementиJSX.Element?interface CardProps { title: string; children: React.ReactNode; // text, elements, arrays, null} function Card({ title, children }: CardProps) {…Как типизировать обработчики событий вроде
onChange,onSubmitиonClick?function SignupForm() { const [email, setEmail] = useState(''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setEmail(e.target.value); };…Как типизировать
useState, если начальное значение не описывает всех возможных состояний?interface User { id: string; name: string;} function Profile() {…Как типизировать ref на DOM-элемент и как компонент принимает
refв React 19?interface TextInputProps extends React.ComponentProps<'input'> { label: string;} // React 19: ref arrives as a regular prop, no forwardReffunction TextInput({ label, ref, ...rest }: TextInputProps) {…Как типизировать компонент-обёртку, который принимает все пропсы нативного элемента, например
Buttonповерх<button>?type ButtonProps = React.ComponentProps<'button'> & { variant?: 'primary' | 'ghost';}; function Button({ variant = 'primary', className, ...rest }: ButtonProps) { return <button className={`btn btn-${variant} ${className ?? ''}`} {...rest} />;…Что такое размеченное объединение (discriminated union) и как оно помогает типизировать состояние и пропсы компонента?
type FetchState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'error'; error: Error } | { status: 'success'; data: T };…Скомпилируется ли этот компонент? Если нет, что сообщит TypeScript и как это исправить?
type ActionProps = | { kind: 'link'; href: string } | { kind: 'button'; onClick: () => void }; function Action(props: ActionProps) { const { kind, href } = props; if (kind === 'link') { return <a href={href}>Open</a>; } return <button onClick={props.onClick}>Run</button>;}Как написать дженерик-компонент, например список или select, который работает с любым типом элементов?
interface ListProps<T> { items: T[]; getKey: (item: T) => string; renderItem: (item: T) => React.ReactNode;}…Как типизировать компонент высшего порядка (HOC), чтобы вызывающему не нужно было передавать пропсы, которые он внедряет?
interface WithUserProps { user: User;} // P is the wrapped component's full props; the HOC supplies `user` itselffunction withUser<P extends WithUserProps>(Component: React.ComponentType<P>) {…Как типизировать контекст React, чтобы потребителям не приходилось каждый раз проверять его на null?
interface AuthContextValue { user: User | null; signOut: () => void;} const AuthContext = createContext<AuthContextValue | null>(null);…Как типизировать экшены
useReducerи как заставить компилятор поймать необработанный экшен?type State = { count: number; step: number };type Action = | { type: 'increment' } | { type: 'setStep'; step: number } | { type: 'reset' };…Как один раз типизировать
useSelectorиuseDispatchиз Redux Toolkit для всего приложения и откуда берутсяRootStateиAppDispatch?// app/store.tsexport const store = configureStore({ reducer: { cart: cartSlice.reducer, user: userSlice.reducer },});export type RootState = ReturnType<typeof store.getState>; // grows with every sliceexport type AppDispatch = typeof store.dispatch; // includes the thunk overloads…Какие утилитарные типы TypeScript вы чаще всего используете в React-коде и для чего?
interface User { id: string; name: string; email: string; role: 'admin' | 'member';}…Что делает оператор
satisfiesи чем он отличается от аннотации типа и отas?type Route = { path: string; title: string }; // annotation: checked, but keys widen to stringconst annotated: Record<string, Route> = { home: { path: '/', title: 'Home' },};…Как типизировать результат кастомного хука, который, как
useState, возвращает кортеж?// wrong: inferred as (boolean | (() => void))[]function useToggleLoose(initial = false) { const [on, setOn] = useState(initial); return [on, () => setOn((v) => !v)];}…Найдите баг: страница профиля падает с
Cannot read properties of undefined (reading 'city'), хотя TypeScript не сообщает ни об одной ошибке.interface User { id: string; name: string; address: { city: string };} async function loadUser(id: string): Promise<User> { const res = await fetch(`/api/users/${id}`); return (await res.json()) as User;} function City({ user }: { user: User }) { return <p>{user.address.city}</p>;}Что включает
strictвtsconfig.jsonи какие дополнительные флаги стоит включить в React-проекте?{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler",…Как типизировать полиморфный компонент с пропсом
as, чтобы<Text as="a">принималhref, а<Text as="span">нет?type PolymorphicProps<E extends React.ElementType, P> = P & { as?: E } & Omit<React.ComponentProps<E>, keyof P | 'as'>; type TextOwnProps = { size?: 'sm' | 'md' | 'lg' }; function Text<E extends React.ElementType = 'span'>({…Как типизировать пропсы, которые либо контролируемые (
valueсonChange), либо неконтролируемые (defaultValue), чтобы смешивание не компилировалось?type Controlled = { value: string; onChange: (value: string) => void; defaultValue?: never;};…Найдите баг: после того как пропсы обернули в
Omit<ButtonProps, 'className'>,props.hrefперестал существовать даже внутри веткиvariant === 'link'.type ButtonProps = | { variant: 'link'; href: string; className?: string } | { variant: 'action'; onClick: () => void; className?: string }; // the primary button sets its own classNametype PrimaryButtonProps = Omit<ButtonProps, 'className'>; function PrimaryButton(props: PrimaryButtonProps) { if (props.variant === 'link') { return <a className="btn-primary" href={props.href}>Open</a>; } return <button className="btn-primary" onClick={props.onClick}>Run</button>;}