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'}
)Якщо ви бажаєте перевірити, як 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="<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>" Це водночас валідний HTML та JSX "</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>