# Введение

JSX является встраиваемым XML-подобным расширением синтаксиса JavaScript. Он должен трасформироваться в корректный JavaScript, однако семантика такого преобразования зависит от конкретной реализации. JSX завоевал популярность вместе с фреймворком React, но потом применялся и отдельно. TypeScript поддерживает встраивание, проверку типов и преобразование JSX в JavaScript напрямую.

Table of Contents #

Основы #

Чтобы начать использовать JSX, необходимо сделать следующее:

  1. Назначить вашим файлам расширение .tsx
  2. Включить опцию 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 #

Вспомним, как записывается декларирование типов:

var foo = bar;

Здесь мы декларируем, что переменная bar будет иметь тип foo. Так как TypeScript также использует угловые скобки для декларирования типов, синтаксис JSX's становится труднее обработать. В результате TypeScript запрещает использование угловых скобок при декларировании типов в файлах .tsx.

Чтобы исправить эту потерю функциональности в файлах .tsx, был добавлен новый оператор: as. Предыдущий пример можно переписать с использованием оператора as.

var foo = bar as foo;




Оператор as доступен как в .ts так и в .tsx файлах и ведёт себя точно также, как и другой оператор декларирования.

Проверка типов #

Чтобы понять проверку типов в JSX, необходимо уяснить разницу между внутренними элементами и элементами, основанными на значении. В JSX-выражении <expr />, expr может означать как внутренний элемент окружения (например, div или span в окружении DOM), так и созданный вами пользовательский элемент. Это важно по следующим причинам:

  1. В React внутренние элементы генерируются в виде строк (React.createElement("div")), а пользовательские компоненты нет (React.createElement(MyComponent)).
  2. Типы атрибутов, передаваемых в JSX-элемент, получаются разными способами. Внутренние элементы должны быть известны по умолчанию, тогда как компоненты, скорее всего, будут создавать свои собственные наборы атрибутов.

TypeScript использует то же соглашение, что и React, чтобы различать два вышеупомянутых случая. Внутренние элементы всегда начинаются с маленькой буквы, а элементы, основанные на значении, начинаются с заглавной.

Внутренние элементы

Система находит внутренние элементы с помощью специального интерфейса JSX.IntrinsicElements. По умолчанию, если этот интерфейс не определён, тип всех внутренних элементов не будет проверен. Однако если интерфейс определён, система будет искать имя внутреннего элемента как свойство интерфейса JSX.IntrinsicElements. Например:

declare namespace JSX {
    interface IntrinsicElements {
        foo: any
    }
}

; // ok
; // ошибка

В примере выше <foo /> отработает нормально, но <bar /> приведёт к ошибке, так как он не был определён в JSX.IntrinsicElements.

Замечание: Вы также можете определить универсальный строковый индексатор JSX.IntrinsicElements:

declare namespace JSX {
   interface IntrinsicElements {
       [elemName: string]: any;
   }
}

Элементы-значения

Система ищет элементы-значения по идентификаторам в пределах области видимости.

import MyComponent from "./myComponent";

; // ok
; // ошибка

Есть возможность ограничить тип элемента-значения. Но для этого необходимо ввести два новых термина: тип класса элемента (element class type) и тип экземпляра элемента (element instance type).

В выражении <Expr /> типом класса элемента является тип Expr. Таким образом, в вышеприведённом примере, если MyComponent принадлежит классу ES6, типом класса будет именно этот класс. Если MyComponent является фабричной функцией, тип класса будет этой функцией.

Как только тип класса установлен, тип экземпляра определяется объединением возвращаемых типов сигнатуры вызова типа класса и сигнатуры конструктора. Опять же, в случае класса ES6, типом экземпляра будет тип экземпляра этого класса, а в случае фабричной функции это будет тип возвращаемого функцией значения.

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 только теми типами, которые соответствуют правильному интерфейсу.

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

declare namespace JSX {
  interface IntrinsicElements {
    foo: { bar?: boolean }
  }
}

// типом атрибутов элемента 'foo' является '{bar?: boolean}'
;

Для элементов-значений вопрос немного усложняется. Тип атрибутов определяется типом типом экземпляра элемента (element instance type), который был установлен ранее. Какое свойство использовать, определяется с помощью JSX.ElementAttributesProperty. Оно должно быть объявлено единственным свойством, имя которого будет использоваться далее.

declare namespace JSX {
  interface ElementAttributesProperty {
    props; // укажите имя свойства для дальнейшего использования
  }
}

class MyComponent {
  // укажите свойство типа экземпляра элемента
  props: {
    foo?: string;
  }
}

// типом атрибутов элемента 'MyComponent' является '{foo?: string}'

Тип атрибута элемента используется для проверки типов атрибутов в JSX. Поддерживаются опциональные и обязательные свойства.

declare namespace JSX {
  interface IntrinsicElements {
    foo: { requiredProp: string; optionalProp?: number }
  }
}

; // ok
; // ok
; // ошибка, не указано requiredProp 
; // ошибка, requiredProp должно быть строкой
; // ошибка, unknownProp не существует
; // ok, потому что 'some-unknown-prop' не является корректным идентификатором

Замечание: Если имя атрибута не является корректным JS-идентификатором (как атрибут data-*), это не будет считаться ошибкой, если не будет находиться в типе атрибутов элемента.

Также можно использовать оператор расширения:

var props = { requiredProp: "bar" };
; // ok

var badProps = {};
; // ошибка

Тип результата JSX #

По умолчанию результирующим типом выражения JSX является тип any. Вы можете изменить тип путём определения интерфейса JSX.Element. Однако невозможно получить информацию о типах элемента, атрибутов или потомков JSX из интерфейса. Фактически это чёрный ящик.

Встраивание выражений #

JSX позволяет вставлять выражения между тегами, заключая их в фигурные скобки ({ }).

var a = 
{["foo", "bar"].map(i => {i / 2})}

      

Вышеприведённый код завершится ошибкой, так как вы не можете разделить строку на число. При использовании опции preserve вывод будет выглядеть так:

var a = 
{["foo", "bar"].map(function (i) { return {i / 2}; })}

      

Интеграция с React #

Для работы JSX с React необходимо использовать React typings. Эти типы определяют пространство имён JSX для корректного использования с React.

/// 

interface Props {
  foo: string;
}

class MyComponent extends React.Component {
  render() {
    return {this.props.foo}
  }
}

; // ok
; // ошибка

Источник







Поддержите перевод документации:



Поддерживатель | Github Репозиторий


Documentation generated by mdoc.
Молния! Обновления, новости и статьи Typescript.