Learning Book

Основы JSX в TypeScript

Основы JSX в TypeScript

Что такое JSX

JSX (JavaScript XML) – синтаксическое расширение, позволяющее описывать структуру UI прямо в коде:

// JSX-выражение — это не строка и не HTML
const element = <h1>Привет, мир!</h1>

TypeScript понимает JSX и добавляет к нему статическую проверку типов: какие атрибуты допустимы, какие children принимает элемент, что возвращает компонент.

Файлы .tsx

Чтобы использовать JSX в TypeScript, файл должен иметь расширение .tsx:

src/
  utils.ts       // обычный TypeScript — JSX недоступен
  App.tsx         // TypeScript + JSX
  Button.tsx      // TypeScript + JSX
В файлах .ts синтаксис JSX вызовет ошибку компиляции. JSX разрешён только в .tsx.

Опция компилятора jsx

В tsconfig.json параметр jsx определяет, как TypeScript обрабатывает JSX-выражения. Есть пять режимов:

РежимВыходРасширениеОписание
preserveJSX сохраняется.jsxДля дальнейшей обработки Babel/SWC
reactReact.createElement(...).jsКлассический React до v17
react-jsx_jsx(...).jsНовый JSX-трансформ React 17+
react-jsxdev_jsxDEV(...).jsТо же + отладочная информация
react-nativeJSX сохраняется.jsДля React Native бандлера
// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

preserve

TypeScript оставляет JSX как есть. Другой инструмент (Babel, esbuild, SWC) выполнит трансформацию:

// Вход (preserve)
const el = <div className="box">Текст</div>

// Выход — JSX сохранён
const el = <div className="box">Текст</div>

react

Классический режим — JSX превращается в вызовы React.createElement:

// Вход (react)
const el = <div className="box">Текст</div>

// Выход
const el = React.createElement("div", { className: "box" }, "Текст")
В режиме react необходим import React from 'react' в каждом файле с JSX, иначе React будет не определён в рантайме.

react-jsx

Новый JSX-трансформ (React 17+). Не требует импорта React:

// Вход (react-jsx)
const el = <div className="box">Текст</div>

// Выход — автоматический импорт
import { jsx as _jsx } from "react/jsx-runtime"
const el = _jsx("div", { className: "box", children: "Текст" })

Оператор as вместо angle-bracket

В обычных .ts файлах приведение типов можно записать двумя способами:

// Angle-bracket синтаксис (только в .ts)
const input = <HTMLInputElement>document.getElementById('name')

// as-синтаксис (работает и в .ts, и в .tsx)
const input = document.getElementById('name') as HTMLInputElement

В .tsx файлах angle-bracket синтаксис <Type>value запрещён, потому что он конфликтует с JSX-тегами:

// Ошибка! TypeScript думает, что <string> — это JSX-элемент
const name = <string>someValue  // ❌

// Правильно — используйте as
const name = someValue as string  // ✅
Рекомендация: используйте as везде, даже в .ts файлах. Так код будет единообразным и легко переносимым в .tsx.

Пример полного tsconfig.json для JSX

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "strict": true,
    "moduleResolution": "bundler",
    "esModuleInterop": true
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"]
}

Когда TypeScript встречает < в .tsx файле, он должен понять: это начало JSX-выражения или оператор «меньше»? Решение принимается по контексту:

// JSX — после = или return ожидается выражение
const el = <div />

// Сравнение — после переменной ожидается оператор
const isSmaller = a < b

// Дженерик в стрелочной функции — конфликт!
// const identity = <T>(x: T): T => x  // ❌ Ошибка

// Решение: добавить extends или запятую
const identity = <T,>(x: T): T => x        // ✅ trailing comma
const identity = <T extends unknown>(x: T): T => x  // ✅ extends

Это ограничение касается только стрелочных функций с одним параметром типа. Обычные function-объявления с дженериками работают без проблем в .tsx.

Итоги

  • JSX в TypeScript работает через файлы .tsx и опцию jsx в tsconfig.json.
  • Режим react-jsx (React 17+) — рекомендуемый: не требует ручного импорта React.
  • В .tsx файлах вместо <Type>value используйте value as Type.
  • Стрелочные дженерик-функции в .tsx требуют trailing comma или extends.