ИНСТР-JSON-TS-REACTReact + TypeScriptType-safe компонентыревизия 2026-05-07

JSON → React Props

Генерация TypeScript Props interface для React компонентов. PropsWithChildren, default values, generic компоненты.

⏱ работает в браузере · без регистрации
Инструмент · ИНСТР-JSON-TS-REACT|real-time
calcal.ru / json-v-typescript-react-props
Загрузка инструмента…
Props
Стандарт naming
type
vs interface
auto
Optional inference
0
Запросов к серверу

Зачем типизировать props

React — наиболее популярная UI-библиотека (40% всех new фронтенд-проектов в 2024 по State of JS). Без TypeScript любой проект быстро превращается в хаос: «какие props принимает этот компонент?», «обязательно ли передавать color?», «можно ли передать null?». Ответ — в коде, который надо читать.

С TypeScript эти вопросы решаются автоматически: IDE показывает доступные props, типы значений, optional/required. Опечатки и неверные типы — ошибка компиляции, не runtime баг. По данным GitHub, проекты с TypeScript имеют на 15% меньше production-ошибок.

Common patterns

1. Базовый Props interface

type ButtonProps = {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg';
};

export function Button({
  label,
  onClick,
  variant = 'primary',
  disabled = false,
  size = 'md',
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant} btn-${size}`}
    >
      {label}
    </button>
  );
}

Все необязательные props имеют default values через деструктуризацию. Variant ограничен union type — IDE подскажет варианты.

2. С children (PropsWithChildren)

import { PropsWithChildren } from 'react';

type CardProps = PropsWithChildren<{
  title: string;
  footer?: React.ReactNode;
}>;

export function Card({ title, footer, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Использование
<Card title="Hello" footer={<button>OK</button>}>
  <p>Card content here</p>
</Card>

3. Event handlers

type FormFieldProps = {
  label: string;
  value: string;
  // Несколько способов типизации:

  // 1. React types (предпочтительно)
  onChange: React.ChangeEventHandler<HTMLInputElement>;

  // 2. Inline event type
  onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;

  // 3. Простая функция
  onValueChange?: (value: string) => void;
};

export function FormField({ label, value, onChange, onBlur, onValueChange }: FormFieldProps) {
  return (
    <label>
      {label}
      <input
        value={value}
        onChange={(e) => {
          onChange(e);
          onValueChange?.(e.target.value);
        }}
        onBlur={onBlur}
      />
    </label>
  );
}

4. Расширение HTML props

// Принимаем все стандартные input attrs (placeholder, name, autoComplete...)
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
  label: string;
  error?: string;
};

export function Input({ label, error, ...inputProps }: InputProps) {
  return (
    <label className="form-input">
      <span>{label}</span>
      <input {...inputProps} />
      {error && <span className="error">{error}</span>}
    </label>
  );
}

// Использование — все стандартные input props доступны
<Input
  label="Email"
  type="email"
  placeholder="ivan@example.com"
  required
  autoComplete="email"
/>
Используйте type для большинства props — он более гибкий (поддерживает union, intersection, mapped types). interface оставьте для public API библиотек где важна возможность расширения через declaration merging.React TypeScript Cheatsheet, 2024

Advanced — generic компоненты

List с произвольным типом item

type ListProps<T> = {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  emptyMessage?: string;
  keyExtractor?: (item: T) => string | number;
};

export function List<T>({
  items,
  renderItem,
  emptyMessage = 'No items',
  keyExtractor,
}: ListProps<T>) {
  if (items.length === 0) return <p>{emptyMessage}</p>;

  return (
    <ul>
      {items.map((item, i) => (
        <li key={keyExtractor ? keyExtractor(item) : i}>
          {renderItem(item, i)}
        </li>
      ))}
    </ul>
  );
}

// Использование
type User = { id: number; name: string };
const users: User[] = [...];

<List<User>
  items={users}
  renderItem={(user) => <UserCard user={user} />}
  keyExtractor={(user) => user.id}
/>

// TS выведет тип User автоматически — generic не обязателен
<List
  items={users}  // TS видит User[]
  renderItem={(user) => <UserCard user={user} />}  // user: User
/>

Discriminated union для разных вариантов

type AlertProps =
  | { type: 'error'; error: Error; message?: never }
  | { type: 'info'; message: string; error?: never }
  | { type: 'success'; message: string; error?: never };

export function Alert(props: AlertProps) {
  if (props.type === 'error') {
    // TS знает что есть error, нет message
    return <div className="alert-error">{props.error.message}</div>;
  }

  // Здесь props.type === 'info' | 'success'
  return (
    <div className={`alert-${props.type}`}>
      {props.message}
    </div>
  );
}

// IDE подскажет required field в зависимости от type:
<Alert type="error" error={new Error('Failed')} />     // ✅
<Alert type="info" message="Hello" />                   // ✅
<Alert type="error" message="Hello" />                  // ❌ ошибка компиляции

Polymorphic component (as prop)

type TextProps<T extends React.ElementType = 'p'> = {
  as?: T;
  children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<T>, 'as' | 'children'>;

export function Text<T extends React.ElementType = 'p'>({
  as,
  children,
  ...rest
}: TextProps<T>) {
  const Component = as || 'p';
  return <Component {...rest}>{children}</Component>;
}

// Использование
<Text>Default p</Text>
<Text as="h1" id="title">Heading</Text>
<Text as="span" className="inline">Inline text</Text>
<Text as="a" href="/about">Link</Text>  // TS знает href — валидный для <a>

forwardRef с типами

type InputProps = {
  label: string;
} & React.InputHTMLAttributes<HTMLInputElement>;

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ label, ...rest }, ref) => {
    return (
      <label>
        {label}
        <input ref={ref} {...rest} />
      </label>
    );
  }
);

Input.displayName = 'Input';

// Использование
const inputRef = useRef<HTMLInputElement>(null);

<Input
  ref={inputRef}
  label="Name"
  placeholder="Иван"
  onChange={(e) => console.log(e.target.value)}
/>

Подводные камни

  • any — плохая идея. Если используете any для props, теряете все преимущества TypeScript. Лучше unknown с runtime-проверкой.
  • defaultProps deprecated. В React 18+ используйте default values в деструктуризации, не Component.defaultProps.
  • Children — необязательное по умолчанию. Если children обязательны — явно: { children: ReactNode }, без знака вопроса.
  • Optional + default = always defined. { color?: string } с default «blue» — внутри функции color всегда string, не undefined.
  • React.FC устарел. Не используйте: const Button: React.FC<Props> = (props) => .... React-team рекомендует обычные функции с типами параметра.
  • Слишком сложные generics. Если interface больше 50 строк — пересмотрите дизайн компонента. Возможно нужно разделить на несколько.
ИСТОЧНИКИ
  1. React TypeScript Cheatsheet. React TypeScript Cheatsheets. react-typescript-cheatsheet.netlify.app. 2024.
  2. React Documentation — TypeScript. React Team. react.dev/learn/typescript. 2024.
  3. Total TypeScript — React Components. Matt Pocock. totaltypescript.com. 2024.
ЧАСТЫЕ ВОПРОСЫ

Часто задаваемые вопросы

Без TypeScript: <code>function Button(props) { ... }</code> — props это any. Можно передать любые данные, опечатка в имени props («onCLick» вместо «onClick») найдётся только в runtime когда кнопка не работает. С типами: <code>function Button(props: ButtonProps)</code> — компилятор подсказывает доступные props, ошибки видны сразу. По данным GitHub Octoverse, 78% React-проектов в 2024 используют TypeScript.
Стандарт: <code>ComponentNameProps</code>. Для Button — ButtonProps. Для UserCard — UserCardProps. Размещайте interface рядом с компонентом или в отдельном types.ts. Современная конвенция (Bulletproof React, Vercel): один файл — один компонент с типами в начале файла. Альтернатива (старая школа): отдельные типы в src/types/.
<code>React.PropsWithChildren&lt;T&gt;</code> — добавляет к вашим props стандартный children. Например: <code>type CardProps = PropsWithChildren&lt;{ title: string }&gt;</code> = <code>{ title: string; children?: ReactNode }</code>. Используется когда компонент принимает children. Альтернатива — явно: <code>{ title: string; children: ReactNode }</code>.
Сделайте props опциональным через ? и используйте default в деструктуризации: <code>type Props = { color?: string };\nfunction Button({ color = "blue" }: Props) { ... }</code>. Старый способ defaultProps deprecated в React 18+. НЕ используйте: <code>Button.defaultProps = { color: "blue" };</code> — это устаревший паттерн.
Используйте встроенные типы React: <code>onClick: React.MouseEventHandler&lt;HTMLButtonElement&gt;</code>, <code>onChange: React.ChangeEventHandler&lt;HTMLInputElement&gt;</code>. Или: <code>onClick: (e: React.MouseEvent&lt;HTMLButtonElement&gt;) =&gt; void</code>. Если функция без параметров — просто <code>() =&gt; void</code>. Старайтесь не использовать <code>any</code> — это убивает type safety.
Для повторно используемых компонентов с разными типами данных. Пример: <code>type ListProps&lt;T&gt; = { items: T[]; renderItem: (item: T) =&gt; ReactNode };\nfunction List&lt;T&gt;({ items, renderItem }: ListProps&lt;T&gt;) { ... }</code>. Использование: <code>&lt;List&lt;User&gt; items={users} renderItem={u =&gt; &lt;UserCard user={u} /&gt;} /&gt;</code>. TS выводит User для items и render.
В большинстве случаев — без разницы. Tradition: interface для public API (можно расширять через declaration merging), type для union types и mapped types. Для props в React большинство команд используют type — он позволяет union types: <code>type Props = { variant: "primary" | "secondary" }</code>. interface это не умеет напрямую (нужно отдельный type для variant).
Required: без знака вопроса. <code>{ title: string }</code> — обязательно передать. Optional: со знаком вопроса. <code>{ description?: string }</code> — может не передавать, тогда undefined. Делайте максимум props optional где возможно — это упрощает использование. Required только если без props компонент бессмыслен.
Лиана Арифметова
АВТОРverifiedред. calcal.ru

Лиана Арифметова

Создатель и главный редактор

Миссия: демократизировать сложные расчёты. Превратить страх перед числами в ясность и контроль. Девиз: «Любая повторяющаяся задача заслуживает своего калькулятора».

Mathematical Engineering · МФТИ · редактирует каталог с 2012 года

Был ли этот калькулятор полезен?

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ

Инструмент справочный — не заменяет эксперта

Только для информационных целей. Все расчёты, результаты и данные, предоставляемые инструментом, носят исключительно ознакомительный и справочный характер. Они не являются профессиональной консультацией — медицинской, юридической, финансовой, инженерной или иной.

Точность результатов. Калькулятор основан на общепринятых формулах и методиках, однако фактические результаты могут отличаться в зависимости от индивидуальных условий, исходных данных и применяемых стандартов. Мы не гарантируем полноту, точность или актуальность приведённых расчётов.

Профессиональные решения — медицинские, финансовые, инженерные — должны приниматься только после консультации с квалифицированным специалистом. Не используйте автоматический расчёт как единственное основание для важных решений.

Ограничение ответственности. Авторы и разработчики сервиса не несут ответственности за прямой или косвенный ущерб, возникший из-за использования данных расчётов. Пользователь принимает на себя всю ответственность за интерпретацию результатов.