Перейти к содержанию

JSX в деталях

JSX — синтаксический сахар для функции React.createElement(component, props, ...children). Этот JSX-код:

1
2
3
<MyButton color="blue" shadowSize={2}>
  Нажми меня
</MyButton>

Скомпилируется в:

1
2
3
4
5
React.createElement(
  MyButton,
  { color: 'blue', shadowSize: 2 },
  'Нажми меня'
)

Вы также можете использовать самозакрывающийся тег, если отсутствуют дочерние элементы. Поэтому код:

1
<div className="sidebar" />

Скомпилируется в:

1
React.createElement('div', { className: 'sidebar' }, null)

Указание типа React-элемента

Первая часть JSX тега определяет тип React-элемента.

Типы, написанные с большой буквы, указывают, что JSX-тег ссылается на React-компонент. Эти теги компилируются в прямую ссылку на именованную переменную, поэтому, если вы используете JSX-выражение <Foo />, то Foo должен быть в области видимости.

React должен находиться в области видимости

Поскольку JSX компилируется в вызов React.createElement, библиотека React должна всегда быть в области видимости вашего JSX-кода.

К примеру, в данном коде оба импорта являются необходимыми, даже если на React и CustomButton нет прямых ссылок из JavaScript:

1
2
3
4
5
6
7
import React from 'react'
import CustomButton from './CustomButton'

function WarningButton() {
  // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />
}

Если вы не используете сборщик JavaScript и загружаете React с помощью тега <script>, то он уже доступен как React в глобальной области видимости.

Использование записи через точку

Вы также можете ссылаться на React-компонент, используя запись через точку. Это удобно, если у вас есть модуль, который экспортирует много React-компонентов. К примеру, если MyComponents.DatePicker является компонентом, то вы можете обратиться к нему напрямую, используя запись через точку:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import React from 'react'

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return (
      <div>
        Представьте, что здесь цвет {props.color} виджета
        выбора даты.
      </div>
    )
  },
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />
}

Названия типов пользовательских компонентов должны начинаться с большой буквы

Если название типа элемента начинается с маленькой буквы, он ссылается на встроенный компонент, к примеру <div> или <span>, что в результате приведёт к тому, что в React.createElement будет передана строка 'div' или 'span'. Типы, начинающиеся с заглавной буквы, такие как <Foo />, компилируются в React.createElement(Foo) и соответствуют компоненту, который был объявлен или импортирован в вашем JavaScript-файле.

Мы рекомендуем называть компоненты с заглавной буквы. Если у вас есть компонент, название которого начинается с маленькой буквы, то перед тем как использовать его в JSX, присвойте его в переменную, которая имеет название с заглавной буквы.

К примеру, этот код будет работать не так, как ожидается:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from 'react'

// Неправильно! Это компонент и он должен быть записан с заглавной буквы:
function hello(props) {
  // Правильно! Использование <div> разрешено, так как это валидный HTML-тег:
  return <div>Привет, {props.toWhat}</div>
}

function HelloWorld() {
  // Неправильно! React думает, что <hello /> - это HTML-тег,
  // потому что он записан с маленькой буквы:
  return <hello toWhat="Мир" />
}

Для того, чтобы исправить это, мы переименуем hello в Hello и станем использовать <Hello />, когда будем ссылаться на него:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from 'react'

// Правильно! Это компонент и он должен быть записан с заглавной буквы:
function Hello(props) {
  // Правильно! Использование <div> разрешено, так как это валидный HTML-тег:
  return <div>Привет, {props.toWhat}</div>
}

function HelloWorld() {
  // Правильно! React знает, что <Hello /> это компонент,
  // потому что он написан с заглавной буквы.
  return <Hello toWhat="Мир" />
}

Выбор типа во время исполнения

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // Неправильно! JSX-тип не может являться выражением
  return <components[props.storyType] story={props.story} />;
}

Чтобы исправить это, мы присвоим тип в переменную, начинающуюся с заглавной буквы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from 'react'
import { PhotoStory, VideoStory } from './stories'

const components = {
  photo: PhotoStory,
  video: VideoStory,
}

function Story(props) {
  // Правильно! JSX-тип может являться переменной, названной с заглавной буквы
  const SpecificStory = components[props.storyType]
  return <SpecificStory story={props.story} />
}

Пропсы в JSX

Существует несколько разных способов передачи пропсов в JSX.

JavaScript-выражения как пропсы

Вы можете передавать любые JavaScript-выражения как пропсы, обернув их в {}. К примеру, в этом JSX:

1
<MyComponent foo={1 + 2 + 3 + 4} />

Для MyComponent значение props.foo будет равно 10, потому что выражение 1 + 2 + 3 + 4 будет вычислено.

Оператор if и цикл for не являются выражениями в JavaScript, поэтому их нельзя непосредственно использовать в JSX. Вместо этого, вы можете окружить ими JSX-код. К примеру:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function NumberDescriber(props) {
  let description
  if (props.number % 2 == 0) {
    description = <strong>чётным</strong>
  } else {
    description = <i>нечётным</i>
  }
  return (
    <div>
      {props.number} является {description} числом
    </div>
  )
}

Вы можете узнать больше про условный рендеринг и циклы в соответствующих разделах.

Строковые литералы

Вы можете передать строковый литерал как проп. Эти два выражения эквивалентны:

1
2
3
<MyComponent message="привет, мир" />

<MyComponent message={'привет, мир'} />

Когда вы передаёте строковый литерал, все его возможные символы будут преобразованы в соответствующие HTML-сущности. Поэтому эти два JSX-выражения будут эквивалентны:

1
2
3
<MyComponent message="&lt;3" />

<MyComponent message={'<3'} />

Обычно такое поведение не должно вас волновать. Оно упомянуто для полноты картины.

Установка пропсов по умолчанию в «true»

Если вы не передаёте значение в проп, то по умолчанию оно будет true. Эти два JSX выражения эквивалентны:

1
2
3
<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

В основном, мы не рекомендуем так делать, потому что это может быть воспринято как сокращение имён свойств из ES6. Тогда, например, {foo} это короткая запись {foo: foo}, но никак не {foo: true}. Такое поведение существует для того, чтобы соответствовать поведению HTML.

Атрибуты расширения

Если у вас уже есть пропсы внутри объекта props и вы хотите передать их в JSX, вы можете использовать оператор расширения .... Эти два компонента эквивалентны:

1
2
3
4
5
6
7
8
function App1() {
  return <Greeting firstName="Иван" lastName="Иванов" />
}

function App2() {
  const props = { firstName: 'Иван', lastName: 'Иванов' }
  return <Greeting {...props} />
}

Вы так же можете выбрать конкретные пропсы, которые ваш компонент будет использовать, передавая все остальные пропсы с помощью оператора расширения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const Button = (props) => {
  const { kind, ...other } = props
  const className =
    kind === 'primary' ? 'PrimaryButton' : 'SecondaryButton'
  return <button className={className} {...other} />
}

const App = () => {
  return (
    <div>
      <Button
        kind="primary"
        onClick={() => console.log('Кнопка нажата!')}
      >
        Привет, мир!
      </Button>
    </div>
  )
}

В приведённом выше примере, проп kind используется безопасно и не передаётся в элемент <button>, находящийся в DOM. Все остальные пропсы передаются с помощью объекта ...other, что делает этот компонент очень гибким. Вы можете видеть, что он передаёт пропсы onClick и children.

Атрибуты расширения могут быть полезны, однако, также они позволяют передать ненужные пропсы в компоненты или невалидные HTML-атрибуты в DOM. Мы рекомендуем использовать этот синтаксис с осторожностью.

Дочерние компоненты в JSX

В JSX-выражениях содержимое, которое расположено между открывающими и закрывающими тегами, передаётся с помощью специального пропа: props.children. Существует несколько способов передать дочерние компоненты:

Строковые литералы

Если вы поместите строку между открывающим и закрывающим тегом, то props.children будет равно этой строке. Это полезно при создании встроенных HTML-элементов. К примеру:

1
<MyComponent>Привет, мир!</MyComponent>

Это корректный JSX-код, в котором значение props.children в MyComponent будет строкой "Привет, мир!". HTML не экранируется, поэтому JSX можно писать также как HTML:

1
<div>Это одновременно и валидный HTML и JSX.</div>

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<div>Привет, мир</div>

<div>
  Привет, мир
</div>

<div>
  Привет,
  мир
</div>

<div>

  Привет, мир
</div>

Дочерние JSX-компоненты

Чтобы отобразить вложенные компоненты, можно указать несколько JSX-элементов в качестве дочерних.

1
2
3
4
<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

Вы можете смешивать различные типы потомков, скажем, использовать строковый литерал вместе с JSX-элементами. Вот ещё один пример, в котором JSX похож на HTML, причём данный код является валидным и для JSX, и для HTML:

1
2
3
4
5
6
7
<div>
  Ниже представлен список:
  <ul>
    <li>Элемент 1</li>
    <li>Элемент 2</li>
  </ul>
</div>

Также React-компонент может возвращать массив элементов:

1
2
3
4
5
6
7
8
9
render() {
  // Не нужно оборачивать список элементов в дополнительный элемент!
  return [
    // Не забудьте про ключи :)
    <li key="A">Первый элемент</li>,
    <li key="B">Второй элемент</li>,
    <li key="C">Третий элемент</li>,
  ];
}

JavaScript-выражения как дочерние компоненты

Вы можете передать любое JavaScript-выражение как дочерний компонент, обернув его в {}. К примеру, эти выражения эквивалентны:

1
2
3
<MyComponent>Пример</MyComponent>

<MyComponent>{'Пример'}</MyComponent>

Часто это бывает полезно при рендере списка JSX-выражений произвольной длины. Например, эта запись рендерит HTML-список:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function Item(props) {
  return <li>{props.message}</li>
}

function TodoList() {
  const todos = [
    'закончить документацию',
    'отправить пулреквест',
    'снова напомнить Дэну про ревью',
  ]
  return (
    <ul>
      {todos.map((message) => (
        <Item key={message} message={message} />
      ))}
    </ul>
  )
}

JavaScript-выражения могут быть использованы вместе с другими типами дочерних компонентов. Они могут рассматриваться как альтернатива шаблонным строкам:

1
2
3
function Hello(props) {
  return <div>Привет, {props.addressee}!</div>
}

Функции как дочерние компоненты

Обычно JavaScript-выражения, вставленные в JSX, будут приведены к строке, React-элементу или списку из всего этого. Тем не менее, props.children работает так же, как и любой другой проп, поэтому в него можно передавать любые типы данных, а не только те, которые React знает как рендерить. К примеру, если у вас есть пользовательский компонент, можно было бы передать колбэк в props.children:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Вызывает колбэк numTimes раз для создания повторяющего компонента
function Repeat(props) {
  let items = []
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i))
  }
  return <div>{items}</div>
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => (
        <div key={index}>
          Это элемент списка с ключом {index}
        </div>
      )}
    </Repeat>
  )
}

Дочерние компоненты, передаваемые пользовательскому компоненту, могут быть чем угодно с тем условием, что компонент преобразует их во что-то, что React сможет понять и отрендерить. Следующий пример редко встречается, но им можно воспользоваться, если необходимо расширить возможности JSX.

Логические значения, null и undefined игнорируются

Значения false, null, undefined и true — валидные дочерние компоненты. Просто они не рендерятся. Эти JSX-выражения будут рендерить одно и то же:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

Этот подход может быть полезным для рендера по условию. Вот пример, где JSX рендерит <Header />, если showHeader равняется true:

1
2
3
4
<div>
  {showHeader && <Header />}
  <Content />
</div>

Есть один нюанс в том, что React будет рендерить «ложные» (falsy) значения, такие как число 0. Код ниже ведёт себя не так, как вы могли ожидать, так как 0 будет отображён, если массив props.messages пуст:

1
2
3
4
5
<div>
  {props.messages.length && (
    <MessageList messages={props.messages} />
  )}
</div>

Чтобы исправить это, убедитесь что выражение перед оператором && всегда является boolean:

1
2
3
4
5
<div>
  {props.messages.length > 0 && (
    <MessageList messages={props.messages} />
  )}
</div>

И наоборот, если вы хотите, чтобы такие значения как false, true, null или undefined отрисовались, то сначала вы должны преобразовать их в строку:

1
<div>Моя переменная JavaScript - {String(myVariable)}.</div>

Комментарии