JSX в деталях

JSX — синтаксичний цукор для функції React.createElement(component, props, ...children). Наступний JSX-вираз:

<MyButton color="blue" shadowSize={2}>
  Натисни мене
</MyButton>

скомпілюється у:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Натисни мене'
)

Також ви можете використати cамозакриваючий тег, якщо відсутні дочірні елементи. Отже вираз:

<div className="sidebar" />

скомпілюється у:

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

Якщо ви бажаєте перевірити, як JSX-вираз компілюється у JavaScript, спробуйте онлайн-компілятор Babel.

Визначення типу React-елемента

Перша частина JSX-тегу вказує на тип React-елемента.

Типи, що починаються з великої літери, вказують, що JSX-тег посилається на React-компонент. Ці теги компілюються в пряме посилання на іменовану змінну, тому Foo має бути в області застосування, якщо ви використовуєте JSX-вираз <Foo />.

React має бути в області застосування

Оскільки JSX компілюється у виклики функції React.createElement, бібліотека React також має бути в області застосування вашого JSX-виразу.

Наприклад, обидва імпорти необхідні в цьому випадку, навіть враховуючи, що React та CustomButton не викликаються напряму в JavaScript:

import React from 'react';
import CustomButton from './CustomButton';

function WarningButton() {
  // повертає React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

Якщо ви не користуєтеся JavaScript-бандлерами та підключаєте React через тег <script>, то він вже доступний через глобальну змінну React.

Використання запису через крапку

Ви також можете посилатися на React-компонент, використовуючи запис через крапку. Це зручно, коли у вас наявний модуль, що експортує багато React-компонентів. Наприклад, якщо MyComponents.DatePicker — компонент, то ви можете посилатися на нього напряму:

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Уявіть тут {props.color} віджет вибору дати.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="блакитний" />;
}

Назви типів компонентів користувача повинні починатися з великої літери

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

Ми рекомендуємо використовувати назви компонентів, що починаються з великих літер. Якщо назва вашого компоненту починається з малої літери, визначте зміну, що починається з великої літери та посилається на ваш компонент, та використовуйте її в JSX.

Наприклад, наступний вираз не буде працювати як очікується:

import React from 'react';

// Невірно! Цей компонент має починатися з великої літери:
function hello(props) {
  // Вірно! Використання <div> тут правомірне, тому що div — валідний HTML-тег:
  return <div>Привіт {props.toWhat}</div>;
}

function HelloWorld() {
  // Невірно! React сприймає <hello /> як HTML-тег, тому що він починається з маленької літери:
  return <hello toWhat="світ" />;
}

Щоб виправити, ми перейменуємо hello у Hello та використаємо <Hello />, коли будемо посилатися на нього:

import React from 'react';

// Вірно! Цей компонент має починатися з великої літери:
function Hello(props) {
  // Вірно! Використання <div> тут правомірне, тому що div — валідний HTML-тег:
  return <div>Привіт {props.toWhat}</div>;
}

function HelloWorld() {
  // Вірно! React знає, що <Hello /> є компонентом, бо він починається з великої літери.
  return <Hello toWhat="світ" />;
}

Вибір типу під час виконання

У якості типу React-елементу не можна використовувати вирази. Якщо ви все ж захочете скористатися виразом для визначення типу елемента, призначте його змінній, що починається з великої літери. Зазвичай це потрібно, якщо ви хочете застосувати різні компоненти в залежності від пропсів:

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} />;
}

Щоб це виправити, спочатку ми привласнимо тип змінній, що починається з великої літери:

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:

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

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

Оператор if та цикл for не є виразами в JavaScript, тому їх не можна використати безпосередньо в JSX. Натомість ви можете застосувати їх в коді, що передує використанню їх результату. Наприклад:

function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>парне</strong>;
  } else {
    description = <i>непарне</i>;
  }
  return <div>{props.number}{description} число</div>;
}

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

Рядкові літерали

Ви можете передати рядковий літерал як проп. Ці два вирази еквівалентні:

<MyComponent message="привіт світ" />

<MyComponent message={'привіт світ'} />

Коли ви передаєте рядковий літерал, всі його символи будуть екрановані відповідно до HTML-сутностей. Тому наступні два записи еквівалентні:

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

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

Зазвичай така поведінка не має вас турбувати. Вона описана для повноти інформації.

Пропси за замовчуванням встановлені в “true”

Якщо ви не передаєте жодного значення пропу, то за замовчуванням його значення дорівнюватиме true. Ці два записи еквівалентні:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

Здебільшого, ми не рекомендуємо робити так, тому що цей запис може бути сплутаний зі скороченням імен властивостей з ES6 Наприклад, {foo} — короткий запис {foo: foo}, а не {foo: true}. Ця поведінка існує, тому що відповідає поведінці HTML.

Розпакування атрибутів

Якщо ви вже маєте визначені пропси в об’єкті props та хочете передати їх в JSX, то ви можете скористатися оператором розпакування ..., щоб це зробити. Ці два компоненти еквівалентні:

function App1() {
  return <Greeting firstName="Іван" lastName="Франко" />;
}

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

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

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, що передаються в <button>.

Розпакування атрибутів може бути корисним, проте з ним легше передати непотрібні пропси в компоненти або невалідні HTML-атрибути в DOM. Ми рекомендуємо використовувати цей синтаксис з обережністю.

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

У JSX-виразах контент, що міститься між відкриваючим та закриваючим тегами, передається через спеціальний проп props.children. Існує декілька варіантів передачі дочірніх елементів:

Рядкові літерали

Ви можете розмістити рядок між відкриваючим та закриваючими тегами, тоді props.children дорівнюватиме цьому рядку. Це корисно при створенні вбудованих HTML-елементів. Наприклад:

<MyComponent>Привіт світ!</MyComponent>

Це приклад валідного JSX, де значення props.children у MyComponent дорівнюватиме рядку "Привіт світ!". HTML не екранується, тому ви можете писати JSX так, наче ви пишете HTML:

<div>&quot; Це водночас валідний HTML та JSX &quot;</div>

JSX прибирає пусті рядки та пробіли на початку та в кінці рядка. Переходи на нові рядки, що прилягають до тегів, видаляються; переходи на нові рядки, що знаходяться між рядковими літералими стискаються до одного пробілу. Таким чином наступні вирази дадуть однаковий результат:

<div>Привіт світ</div>

<div>
  Привіт світ
</div>

<div>
  Привіт
  світ
</div>

<div>

  Привіт світ
</div>

Дочірні JSX-елементи

Для відображення вкладених компонентів, можна передавати декілька JSX-елементів:

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

Ви можете поєднувати різні типи нащадків разом і у такий спосіб можна використовувати рядкові літерали разом з JSX-елементами. Це ще один приклад того, як JSX схожий на HTML, а тому наступний код валідний як для JSX, так і для HTML:

<div>
  Наступним йде список:
  <ul>
    <li>Елемент 1</li>
    <li>Елемент 2</li>
  </ul>
</div>

Також React-компонент може повертати масив елементів:

render() {
  // Немає необхідності обгортати список елементів у додатковий елемент!
  return [
    // Не забудьте про ключі :)
    <li key="A">Перший елемент</li>,
    <li key="B">Другий елемент</li>,
    <li key="C">Третій елемент</li>,
  ];
}

JavaScript-вирази як дочірні елементи

Ви можете передати будь-який JavaScript-вираз як дочірній елемент, записавши його у фігурних дужках {}. Наприклад, ці вирази еквівалентні:

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

Часто це буває корисним для рендерингу списку JSX-виразів довільної довжини. Наприклад, цей код рендерить HTML-список:

function Item(props) {
  return <li>{props.message}</li>;
}

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

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

function Hello(props) {
  return <div>Привіт {props.addressee}!</div>;
}

Функції як дочірні елементи

Зазвичай JavaScript-вирази, що прописані в JSX, перетворюються в рядок, React-елемент або список з попередніх. Проте props.children працює так само, як і будь-який інший проп, тому він може передавати будь-який тип даних, а не тільки ті, що React знає як рендерити. Наприклад, якщо ви маєте компонент користувача, ви можете передати функцію зворотнього виклику у props.children:

// Викликати 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-вирази дадуть однаковий результат:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

Це може бути корисним при умовному рендерингу React-елементів. Наступний JSX-вираз відрендерить <Header /> за умови, що showHeader дорівнює true:

<div>
  {showHeader && <Header />}
  <Content />
</div>

Існує одне зауваження, що React все одно рендерить “неправдиві” значення, такі як число 0. Наприклад, наступний код відпрацює не так, як очікується, тому що 0 буде виведено, коли props.messages буде пустим масивом:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

Щоб це виправити, будьте певні, що вираз перед оператором && — логічне значення:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

Якщо ви хочете, щоб значення false, true, null чи undefined навпаки потрапили до результату, вам необхідно попередньо перетворити їх у рядок:

<div>
  Моя Javascript змінна — {String(myVariable)}.
</div>