# Введение
JSX является встраиваемым XML-подобным расширением синтаксиса JavaScript. Он должен трасформироваться в корректный JavaScript, однако семантика такого преобразования зависит от конкретной реализации. JSX завоевал популярность вместе с фреймворком React, но потом применялся и отдельно. TypeScript поддерживает встраивание, проверку типов и преобразование JSX в JavaScript напрямую.
Table of Contents #
Основы #
Чтобы начать использовать JSX, необходимо сделать следующее:
- Назначить вашим файлам расширение
.tsx
- Включить опцию
jsx
TypeScript имеет два JSX режима: preserve
и react
. Эти режимы влияют только на стадию генерации - проверка типов не изменяется. Режим preserve
сохраняет JSX в выходном коде, который далее передаётся на следующий шаг трансформации (e.g. Babel). Выходной код получит расширение .jsx
. Режим react
сгенерирует React.createElement
, где уже не нужно трансформировать JSX перед применением, и код на выходе получит расширение .js
.
Режим | Вход | Выход | Выходное расширение файла -----------|-----------|------------------------------|-------------------------- preserve
| <div />
| <div />
| .jsx
react
| <div />
| React.createElement("div")
| .js
Вы можете указать режим либо с помощью флага командной строки --jsx
, либо в соответствующей опции в вашем файле tsconfig.json.
Замечание: Идентификатор
React
жёстко прописан в коде, поэтому необходимо сделать React доступным с заглавной буквы R.
Оператор as
#
Вспомним, как записывается декларирование типов:
1 | var foo = bar; |
Здесь мы декларируем, что переменная bar
будет иметь тип foo
. Так как TypeScript также использует угловые скобки для декларирования типов, синтаксис JSX's становится труднее обработать. В результате TypeScript запрещает использование угловых скобок при декларировании типов в файлах .tsx
.
Чтобы исправить эту потерю функциональности в файлах .tsx
, был добавлен новый оператор: as
. Предыдущий пример можно переписать с использованием оператора as
.
1 | var foo = bar as foo; |
Оператор as
доступен как в .ts
так и в .tsx
файлах и ведёт себя точно также, как и другой оператор декларирования.
Проверка типов #
Чтобы понять проверку типов в JSX, необходимо уяснить разницу между внутренними элементами и элементами, основанными на значении. В JSX-выражении <expr />
, expr
может означать как внутренний элемент окружения (например, div
или span
в окружении DOM), так и созданный вами пользовательский элемент. Это важно по следующим причинам:
- В React внутренние элементы генерируются в виде строк (
React.createElement("div")
), а пользовательские компоненты нет (React.createElement(MyComponent)
). - Типы атрибутов, передаваемых в JSX-элемент, получаются разными способами. Внутренние элементы должны быть известны по умолчанию, тогда как компоненты, скорее всего, будут создавать свои собственные наборы атрибутов.
TypeScript использует то же соглашение, что и React, чтобы различать два вышеупомянутых случая. Внутренние элементы всегда начинаются с маленькой буквы, а элементы, основанные на значении, начинаются с заглавной.
Внутренние элементы
Система находит внутренние элементы с помощью специального интерфейса JSX.IntrinsicElements
. По умолчанию, если этот интерфейс не определён, тип всех внутренних элементов не будет проверен. Однако если интерфейс определён, система будет искать имя внутреннего элемента как свойство интерфейса JSX.IntrinsicElements
. Например:
1 2 3 4 5 6 7 8 | declare namespace JSX { interface IntrinsicElements { foo: any } } ; // ok ; // ошибка |
В примере выше <foo />
отработает нормально, но <bar />
приведёт к ошибке, так как он не был определён в JSX.IntrinsicElements
.
Замечание: Вы также можете определить универсальный строковый индексатор
JSX.IntrinsicElements
:
12345declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
Элементы-значения
Система ищет элементы-значения по идентификаторам в пределах области видимости.
1 2 3 4 | import MyComponent from "./myComponent" ; ; // ok ; // ошибка |
Есть возможность ограничить тип элемента-значения. Но для этого необходимо ввести два новых термина: тип класса элемента (element class type) и тип экземпляра элемента (element instance type).
В выражении <Expr />
типом класса элемента является тип Expr
. Таким образом, в вышеприведённом примере, если MyComponent
принадлежит классу ES6, типом класса будет именно этот класс. Если MyComponent
является фабричной функцией, тип класса будет этой функцией.
Как только тип класса установлен, тип экземпляра определяется объединением возвращаемых типов сигнатуры вызова типа класса и сигнатуры конструктора. Опять же, в случае класса ES6, типом экземпляра будет тип экземпляра этого класса, а в случае фабричной функции это будет тип возвращаемого функцией значения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class MyComponent { render() {} } // использование сигнатуры конструктора var myComponent = new MyComponent(); // element class type => MyComponent // element instance type => { render: () => void } function MyFactoryFunction() { return { render: () => { } } } // использование сигнатуры вызова var myComponent = MyFactoryFunction(); // element class type => FactoryFunction // element instance type => { render: () => void } |
Также вызывает интерес тип элемента экземпляра, поскольку должна быть возможность назначить его JSX.ElementClass
, в противном случае возникнет ошибка. По умолчанию JSX.ElementClass
является{}
, но он может быть дополнен, чтобы ограничить использование JSX только теми типами, которые соответствуют правильному интерфейсу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | declare namespace JSX JSX { interface ElementClass { render: any; } } class MyComponent { render() {} } function MyFactoryFunction() { return { render: () => {} } } ; // ok ; // ok class NotAValidComponent {} function NotAValidFactoryFunction() { return {}; } ; // ошибка ; // ошибка |
Проверка типа атрибута
Для того, чтобы проверить типы атрибутов, сначала необходимо определить тип атрибутов элемента (element attributes type). Эта процедура немного отличается для внутренних элементов и элементов-значений.
Для внутренних элементов это тип свойства в JSX.IntrinsicElements
1 2 3 4 5 6 7 8 | declare namespace JSX { interface IntrinsicElements { foo: { bar?: boolean } } } // типом атрибутов элемента 'foo' является '{bar?: boolean}' ; |
Для элементов-значений вопрос немного усложняется. Тип атрибутов определяется типом типом экземпляра элемента (element instance type), который был установлен ранее. Какое свойство использовать, определяется с помощью JSX.ElementAttributesProperty
. Оно должно быть объявлено единственным свойством, имя которого будет использоваться далее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | declare namespace JSX { interface ElementAttributesProperty { props; // укажите имя свойства для дальнейшего использования } } class MyComponent { // укажите свойство типа экземпляра элемента props: { foo?: string; } } // типом атрибутов элемента 'MyComponent' является '{foo?: string}' |
Тип атрибута элемента используется для проверки типов атрибутов в JSX. Поддерживаются опциональные и обязательные свойства.
1 2 3 4 5 6 7 8 9 10 11 12 | declare namespace JSX { interface IntrinsicElements { foo: { requiredProp: string; optionalProp?: number } } } ; // ok ; // ok ; // ошибка, не указано requiredProp ; // ошибка, requiredProp должно быть строкой ; // ошибка, unknownProp не существует ; // ok, потому что 'some-unknown-prop' не является корректным идентификатором |
Замечание: Если имя атрибута не является корректным JS-идентификатором (как атрибут
data-*
), это не будет считаться ошибкой, если не будет находиться в типе атрибутов элемента.
Также можно использовать оператор расширения:
1 2 3 4 5 | var props = { requiredProp: "bar" }; ; // ok var badProps = {}; ; // ошибка |
Тип результата JSX #
По умолчанию результирующим типом выражения JSX является тип any
. Вы можете изменить тип путём определения интерфейса JSX.Element
. Однако невозможно получить информацию о типах элемента, атрибутов или потомков JSX из интерфейса. Фактически это чёрный ящик.
Встраивание выражений #
JSX позволяет вставлять выражения между тегами, заключая их в фигурные скобки ({ }
).
1 | var a = |
Вышеприведённый код завершится ошибкой, так как вы не можете разделить строку на число. При использовании опции preserve
вывод будет выглядеть так:
1 | var a = |
Интеграция с React #
Для работы JSX с React необходимо использовать React typings. Эти типы определяют пространство имён JSX
для корректного использования с React.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /// interface Props { foo: string; } class MyComponent extends React.Component { render() { return <span>{ this .props.foo}</span> } } ; // ok ; // ошибка |
Поддержите перевод документации:
Documentation generated by mdoc.