DRAFT [2022-2023][ua] at 2023-05-03 17:18:24 +0300
Logo-do [errata] Profile

Frontend-розробка SPA Web-додатків

Знайомство з JSX. Управління станом компонента

Конспект лекції


20 БІЛЬШ ДЕТАЛЬНІШЕ ПРО JSX

Давайте розберемося, що відбувається з JSX кодом після того, як ми його написали. Як виходить HTML-код, який ви бачите в браузері? Погляньте на наступний приклад, де визначається компонент з ім'ям Card:

class Card extends React.Component {
render() {
var cardStyle = {
height: 200,
width: 150,
padding: 0,
backgroundColor: "#FFF",
boxShadow: "0px 0px 5px #666"
};
return (
<div style={cardStyle}>
<Square color={this.props.color} />
<Label color={this.props.color} />
</div>
);
}
}

Можна швидко визначити код JSX. Це код, показаний нижче:

<div style={cardStyle}>
<Square color={this.props.color} />
<Label color={this.props.color} />
</div>

Браузери не знають, що робити з JSX. Для цього використовують такі інструменти, як Babel, що дозволяють перетворити JSX-код в те, що розуміють браузери: JavaScript.

return React.createElement(
"div",
{style: cardStyle},
React.createElement(Square, {color: this.props.color}),
React.createElement(Label, {color: this.props.color})
);

Ось як виглядає весь код компонента Card, перетворений в JavaScript:

class Card extends React.Component {
render() {
var cardStyle = {
height: 200,
width: 150,
padding: 0,
backgroundColor: "#FFF",
boxShadow: "0px 0px 5px #666"
};
return React.createElement(
"div",
{style: cardStyle},
React.createElement(Square, {color: this.props.color}),
React.createElement(Label, {color: this.props.color})
);
}
}

Зверніть увагу, що ніде немає JSX-коду. Трансформація відбувається завдяки інструменту Babel і він перетворюється в прийнятний JavaScript-код.

Особливості роботи з JSX-кодом

Обробка виразів

Код JSX обробляється як JavaScript. Це означає, що ви не обмежені роботою зі статичним контентом, наприклад:

class Stuff extends React.Component {
render() {
return (
<h1>Статичний контент</h1>
);
}
};

Значення, що повертаються можуть бути згенеровані динамічно. Все, що вам потрібно зробити, це укласти вираз в фігурні дужки:

class Stuff extends React.Component {
render() {
return (
<h1>Статичний {Math.random() * 100} контент</h1>
);
}
};

Ці фігурні дужки дозволяють додатку спочатку обробити вираз, а потім повернути результат обробки. Без дужок ви побачите, що вираз повертається як текст: Статичний {Math.random() * 100} контент.

Повернення кількох елементів

У багатьох наших прикладах ми повертали один елемент верхнього рівня (div), який включав багато інших елементів. Тут немає обмежень: фактично ви можете повернути кілька елементів і зробити це двома способами.

Один із способів - використовувати масивоподібний синтаксис:

class Stuff extends React.Component {
render() {
return (
[
<p>Я</p>,
<p>повертаю список</p>,
<p>елементів</p>
]
);
}
}

Тут ми повертаємо три елементи p. У них немає єдиного предка. Тепер, коли ви повертаєте декілька елементів, ви можете мати справу або не мати з одним елементом, в залежності від версії React, яку використовуєте. Вам потрібно вказати атрибут key і унікальне значення для кожного елемента:

class Stuff extends React.Component {
render() {
return (
[
<p key="1">Я</p>,
<p key="2">повертаю список</p>,
<p key="3">елементів</p>
]
);
}
}

Так React простіше зрозуміти, з яким елементом він має справу, і вносити будь-які зміни в нього. Як дізнатися, чи потрібно додавати атрибут key? React підкаже вам. Ви побачите повідомлення, подібне до такого, виведене в оболонці командного рядка: Warning: Each child in an array or iterator should have a unique "key".

Існує також інший (і, можливо, кращий) спосіб повернення декількох елементів. Він пов'язаний з так званими фрагментами. Їх застосовують таким чином:

class Stuff extends React.Component {
render() {
return (
<React.Fragment>
<p>Я</p>
<p>повертаю список</p>
<p>елементів</p>
</React.Fragment>
);
}
}

Ви обертаєте список елементів, який хочете повернути, в компонент React.Fragment. Зверніть увагу на кілька цікавих моментів:

  1. 1. Цей компонент фактично не генерує елемент DOM. Ви лише вказуєте його в JSX-коді, який перетворюється в HTML-код, підтримуваний браузером.
  2. 2. Ви не вказуєте конкретно, що повертаєте елементи масиву, тому вам не потрібні коми або інші символи, що розділяють елементи.
  3. 3. Немає необхідності вказувати унікальний атрибут і значення key; все це буде зроблено за вас.

Ви можете використовувати більш лаконічний синтаксис замість повного вказання компонента React.Fragment. Ви можете використовувати тільки символи <> і </>:

class Stuff extends React.Component {
render() {
return (
<>
<p>Я</p>,
<p>повертаю список</p>,
<p>елементів</p>
</>
);
}
}

Не можна використовувати вбудовані стилі CSS

Атрибут style в JSX-коді поводиться інакше, ніж в HTML-коді. У HTML-коді ви можете безпосередньо вказати властивості CSS в якості значень атрибуту style:

<div style="font-family: Arial; font-size:24px">
<p>Контент</p>
</div>

У JSX-коді атрибут style не може містити каскадні таблиці стилів. Замість цього він повинен посилатися на об'єкт, що містить інформацію про стилі:

class Letter extends React.Component {
render() {
var letterStyle = {
padding: 10,
margin: 10,
backgroundColor: this.props.bgcolor,
color: "#333",
display: "inline-block",
fontFamily: "monospace",
fontSize: "32",
textAlign: "center"
};
return (
<div style={letterStyle}>
{this.props.children}
</div>
);
}
}

Зверніть увагу, що використовується об'єкт з ім'ям letterStyle, який містить імена властивостей CSS і їх значення. Цей об'єкт ми вказуємо як атрибут style.

Коментарі

Так само, як і в мовах HTML, CSS і JavaScript, не буде зайвим забезпечити коментарями і JSX-код. Написання коментарів у JSX аналогічно тому, як ви це робите в JavaScript, за одним винятком. Якщо ви вказуєте коментар як дочірній елемент елемента, вам потрібно укласти коментар в дужки {}, щоб переконатися, що він аналізується як вираз:

ReactDOM.render(
<div className="slideIn">
<p className="emphasis">Контент</p>
{/* Дочірній коментар */}
<Label/>
</div>,
document.querySelector("#container")
);

Коментар в цьому випадку - нащадок елемента div. Як варіант, можна вказати однорядковий або багаторядковий коментар цілком всередині тега елемента, не використовуючи фігурні дужки {}.

Регістр, HTML-елементи і компоненти

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

ReactDOM.render(
<div>
<section>
<p>Контент</p>
</section>
</div>,
document.querySelector("#container")
);

Якщо ви хочете вказати імена компонентів, вони повинні бути написані з великої літери:

ReactDOM.render(
<div>
<MyCustomComponent/>
</div>,
document.querySelector("#container")
);

Якщо ви помилилися з регістром, React не зможе правильно візуалізувати контент.

JSX-код може бути де завгодно

У багатьох ситуаціях JSX-код не буде акуратно розміщуватися всередині функції render або return, як в прикладах, які ви бачили до сих пір. Погляньте на наступний приклад:

var swatchComponent = <Swatch color="#2F004F"></Swatch>;
ReactDOM.render(
<div>
{swatchComponent}
</div>,
document.querySelector("#container")
);

Тут є змінна swatchComponent, яка ініціалізується як рядок JSX-коду. Коли змінна swatchComponent поміщається всередину функції render, ініціалізується компонент Swatch. Все це абсолютно справедливо.

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

21 РОБОТА ЗІ СТАНАМИ В REACT

До цього моменту компоненти, які ми створили, не мали станів. У них є властивості (props), які передаються від їхнього предка, але ніщо (зазвичай) не змінює їх після того, як компоненти починають працювати. Об'єкти вважаються незмінними після їх установки. Для багатьох інтерактивних сценаріїв це неприпустимо. Потрібна можливість змінювати аспекти компонентів в результаті взаємодії з користувачем (або деякі дані, які повертаються з сервера, і т.д.).

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

Використання станів

Розглянемо приклад: лічильник блискавок, як показано на рисунку нижче.

Цей приклад не робить нічого надприродного. За даними журналу National Geographic, блискавка вражає земну поверхню близько 100 разів на секунду. У нас є лічильник, який збільшує число, яке ви бачите, з тим же коефіцієнтом. Давайте створимо додаток.

Початок роботи

Основна увага в цьому прикладі - подивитися, як ми можемо працювати з станами. Створіть HTML-документ наступного змісту:

<! DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Стани</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
class LightningCounter extends React.Component {
render() {
return (
<h1>Привіт!</h1>
);
}
}

class LightningCounterDisplay extends React.Component {
render() {
var divStyle = {
width: 250,
textAlign: "center",
backgroundColor: "black",
padding: 40,
fontFamily: "sans-serif",
color: "#999",
borderRadius: 10
};
return (
<div style={divStyle}>
<LightningCounter/>
</div>
);
}
}

ReactDOM.render(
<LightningCounterDisplay/>,
document.querySelector("#container")
);
</script>
</body>
</html>

Тепер подивимося, що робить цей код. По-перше, у нас є компонент з ім'ям LightningCounterDisplay. Основна частина цього компонента - це об'єкт divStyle, який містить інформацію про стилі, що відповідають за округлий фон. Функція return повертає елемент div, в який обгорнутий компонент LightningCounter. Компонент LightningCounter - це місце, де всі дії будуть виконуватися:

class LightningCounterDisplay extends React.Component {
render() {
return (
<div style={divStyle}>
<LightningCounter/>
</div>
);
}
}

Останнє, що потрібно розглянути, це метод ReactDOM.render:

ReactDOM.render(
<LightningCounterDisplay/>,
document.querySelector("#container")
);

Він проштовхує компонент LightningCounterDisplay в елемент container в моделі DOM. Зрештою отримуємо комбінацію розмітки з методу ReactDOM.render і компонентів LightningCounterDisplay і LightningCounter.

Налаштування лічильника

Для налаштування лічильника ми будемо використовувати функцію setInterval, яка викликає певний код кожні 1000 мілісекунд (1 секунду). Цей «певний код» буде збільшувати значення на 100 кожен раз, коли викликається.

Щоб все запрацювало, ми покладаємося на API-інтерфейси, які надає компонент React:

1. componentDidMount – цей метод викликається відразу після того, як компонент візуалізується (рендерится) (або монтується на мові React).

2. setState – цей метод дозволяє оновити значення state об'єкта.

Установка початкового значення стану

Нам потрібна змінна, яка буде виконувати роль лічильника.

У нашому прикладі змінна strikes є частиною стану компонента. Нам потрібно створити об'єкт state, зробити змінну strikes його властивістю і переконатися, що ми це все зробили до того, як компонент буде створений. Компонент, який ми хочемо зробити, носить ім'я LightningCounter:

class LightningCounterDisplay extends React.Component {

constructor(props) {
super(props);
this.state = {
strikes: 0
};
}

render() {
return (
<div style={divStyle}>
<LightningCounter/>
</div>
);
}
}

Ми вказуємо об'єкт state всередині конструктора компонента LightningCounter. Ця дія має бути виконана до того, як компонент буде візуалізовано. Ми повідомляємо React, щоб він створив об'єкт, що містить властивість strikes.

Якщо ми перевіримо значення об'єкта state після запуску цього коду, результат буде таким:

var state = {
strikes: 0
};

Для візуалізації даного лічильника використаємо наступний код:

class LightningCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
strikes: 0
};
}
render() {
return (
<h1>{this.state.strikes}</h1>
);
}
}

Ми замінили вміст на вираз, що відображає значення, збережене властивістю this.state.strikes. Якщо ви переглянете свій приклад в браузері, ви побачите значення 0.

Запуск таймера і встановлення стану

Далі, ми отримуємо значення таймера і збільшуємо значення властивості strikes. Використаємо функцію setInterval, щоб збільшувати значення властивості strikes на 100 щосекунди. Ми збираємося зробити все це відразу після того, як компонент буде візуалізовано за допомогою вбудованого методу componentDidMount. Код запуску таймера виглядає наступним чином:

class LightningCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
strikes: 0
};
}

componentDidMount() {
setInterval(this.timerTick, 1000);
}

render() {
return (
<h1>{this.state.strikes}</h1>
);
}
}

Далі додамо ці виділені рядки в код додатку. Усередині методу componentDidMount, який викликається після того, як компонент рендерится (візуалізується), у нас є метод setInterval, який викликає функцію timerTick кожну секунду (або 1000 мілісекунд).

Визначимо функцію timerTick:

class LightningCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
strikes: 0
};
}

timerTick() {
this.setState({
strikes: this.state.strikes + 100
});
}

componentDidMount() {
setInterval(this.timerTick, 1000);
}

render() {
return (
<h1>{this.state.strikes}</h1>
);
}
}

Функція timerTick викликає метод setState. Цей метод використовується в різних випадках, але в нашому прикладі він приймає об'єкт в якості аргументу. Цей об'єкт містить всі властивості, які потрібно передати в об'єкт state. У нашому випадку ми вказуємо властивість strikes і присвоюємо їй значення 100, щоб вона була більше, ніж спочатку.

Функція timerTick додана в компонент, але її вміст не має контексту, встановленого для нашого компонента. Іншими словами, це ключове слово, за допомогою якого ми звертаємося до setState, поверне в поточній ситуації TypeError. Доступно декілька рішень. На даний момент ми навмисно прив'язуємо функцію timerTick до компоненту, щоб всі посилання this працювали коректно. Додайте наступний рядок в код конструктора:

constructor(props) {
super(props);
this.state = {
strikes: 0
};
this.timerTick = this.timerTick.bind(this);
}

Візуалізація зміни стану

Якщо запустити додаток, то можна побачити, що змінна strikes починає збільшуватися на 100 з кожною секундою.

Оновлений вивід на екран пов'язаний з поведінкою React: всякий раз, коли ви викликаєте метод setState і оновлюєте що-небудь в об'єкті стану, метод render компонента викликається автоматично. Так послідовно викликається метод render будь-якого компонента. Синхронізувати зберігання даних і призначений для користувача інтерфейс - одна з найскладніших проблем при розробці користувальницького інтерфейсу, тому приємно, що React подбав про це за нас.

<! DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Стани</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
class LightningCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
strikes: 0
};
this.timerTick = this.timerTick.bind(this);
}

timerTick() {
this.setState({
strikes: this.state.strikes + 100
});
}

componentDidMount() {
setInterval(this.timerTick, 1000);
}

render() {
var counterStyle = {
color: "#66FFFF",
fontSize: 50
};
var count = this.state.strikes.toLocaleString();
return (
<h1 style={counterStyle}>{count}</h1>
);
}
}

class LightningCounterDisplay extends React.Component {
render() {
var commonStyle = {
margin: 0,
padding: 0
};
var divStyle = {
width: 250,
textAlign: "center",
backgroundColor: "#020202",
padding: 40,
fontFamily: "sans-serif",
color: "#999999",
borderRadius: 10
};
var textStyles = {
emphasis: {
fontSize: 38,
...commonStyle
},
smallEmphasis: {
...commonStyle
},
small: {
fontSize: 17,
opacity: 0.5,
...commonStyle
}
};
return (
<div style={divStyle}>
<LightningCounter/>
<h2 style={textStyles.smallEmphasis}>СПАЛАХИ БЛИСКАВКИ</h2>
<h2 style={textStyles.emphasis}>НА ПЛАНЕТІ</h2>
<p style={textStyles.small}>(з момента запуску коду)</p>
</div>
);
}
}

ReactDOM.render(
<LightningCounterDisplay/>,
document.querySelector("#container")
);

</script>
</body>
</html>

22 РОБОТА З КОРИСТУВАЛЬНИЦЬКИМ ІНТЕРФЕЙСОМ

Щоб охопити все, що вам належить дізнатися, розглянемо приклад.

<! DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Робота з інтерфейсом</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<style>
#container {
padding: 50px;
background-color: #FFF;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
class Circle extends React.Component {
render() {
var circleStyle = {
padding: 10,
margin: 20,
display: "inline-block",
backgroundColor: this.props.bgColor,
borderRadius: "50%",
width: 100,
height: 100,
};
return (
<div style={circleStyle}>
</div>
);
}
}

ReactDOM.render(
<div>
<Circle bgColor="#F9C240"/>
</div>,
document.querySelector("#container")
)
</script>
</body>
</html>

Результат виконання коду в браузері:

Основна частина надходить з компонента Circle:

class Circle extends React.Component {
render() {
var circleStyle = {
padding: 10,
margin: 20,
display: "inline-block",
backgroundColor: this.props.bgColor,
borderRadius: "50%",
width: 100,
height: 100,
};
return (
<div style={circleStyle}>
</div>
);
}
}

Він в основному складається з об'єкта circleStyle, який містить вбудовані властивості стилю, здатні перетворювати контейнер div в коло. Всі налаштування стилів жорстко закодовані, за винятком властивості backgroundColor, яка бере своє значення з змінної bgColor.

За межами компонента ми в підсумку показуємо коло за допомогою звичайного методу ReactDOM.render:

ReactDOM.render(
<div>
<Circle bgColor="#F9C240"/>
</div>,
document.querySelector("#container")
)

У нас є один екземпляр оголошеного компонента Circle, і ми оголошуємо його за допомогою властивості bgColor зі значенням бажаного кольору. Тепер, коли компонент Circle визначається всередині методу render, ми злегка обмежені, особливо якщо будемо мати справу з даними, здатними вплинути на компонент Circle.

JSX фактично може перебувати поза функцією render і використовуватися як значення, присвоєне змінній або властивості.

var theCircle = <Circle bgColor="#F9C240" />;
ReactDOM.render(
<div>
{theCircle}
</div>,
destination
);

Змінна theCircle зберігає JSX-код для створення екземпляра компонента Circle. Ініціалізація цієї змінної всередині функції ReactDOM.render призводить до відображення кола. Кінцевий результат нічим не відрізняється від того, що ми мали раніше, але звільнення екземпляра компонента Circle з методу render дає нам більше можливостей робити наступне: створити функцію, яка повертає компонент Circle.

function showCircle() {
var colors = ["#393E41", "#E94F37", "#1C89BF", "#A1D363"];
var ran = Math.Floor(Math.random() * colors.length);
//Повертає коло з випадковим кольором
return <Circle bgColor={colors[ran]}/>;
}

У цьому випадку функція showCircle повертає компонент Circle з властивістю bgColor, якому присвоєно випадкове значення кольору. Щоб в коді використовувати функцію showCircle, необхідно обробити її всередині методу ReactDOM.render.

ReactDOM.render(
<div>
{showCircle()}
</div>,
destination
);

Робота з масивами

Коли потрібно відобразити кілька компонентів, ви не завжди можете вручну вказати їх:

ReactDOM.render(
<div>
{showCircle()},
{showCircle()},
{showCircle()}
</div>,
destination
);

У багатьох реальних сценаріях кількість відображуваних компонентів пов'язане з кількістю елементів в масивах або масивоподібних структурах, з якими ви працюєте. Це викликає деякі ускладнення. Наприклад, припустимо, що у нас є масив colors, який виглядає наступним чином:

var colors = ["#393E41", "#E94F37", "#1C89BF", "#A1D363",
"#85FFC7", "#297373", "#FF8552", "#A40E4C"];

Нам потрібно створити компонент Circle для кожного елемента в цьому масиві (і встановити значення властивості bgColor для кожного елемента масиву). Ми можемо зробити це, створивши масив компонентів Circle:

var renderData = [];
for (var i = 0; i < colors.length; i++) {
renderData.push(<Circle bgColor={colors[i]} />);
}

У цьому лістингу ми заповнюємо масив renderData компонентами Circle.

ReactDOM.render(
<div>
{showCircle}
</div>,
destination
);

Все, що ми зробили, це в методі render вказали масив renderData як вираз, який потрібно обробити. Нам не потрібно робити ніяких інших кроків, щоб перейти від масиву компонентів до представлення в браузері.

React оновлює користувальницький інтерфейс дуже швидкими темпами, маючи гарне уявлення про те, що саме відбувається в DOM. Він робить це кількома способами. Один з них це внутрішнє позначення кожного елемента якимось ідентифікатором.

Коли ви створюєте елементи динамічно, ці ідентифікатори не встановлюються автоматично. Нам потрібно зробити це самостійно. Потрібно встановити властивість key, значення якого React використовує для ідентифікації кожного конкретного компонента.

for (var i = 0; i < colors.length; i++) {
var color = colors[i];
renderData.push(<Circle key={i + color} bgColor={color} />);
}

Для кожного компонента ми вказуємо властивість key зі значенням у вигляді комбінації змінної color і індексу елемента масива colors. Це гарантує, що кожен компонент, який ми динамічно створюємо, отримує унікальний ідентифікатор, який React може потім використовувати для оптимізації будь-яких майбутніх оновлень призначеного для користувача інтерфейсу.

23 ПОДІЇ В REACT

У більшості додатків, особливо зі складними для користувача інтерфейсами можна виконувати дії у відповідь на якісь події. Ці події можуть бути викликані натисканням миші, натисканням клавіші, зміною розміру вікна або цілою низкою інших жестів і взаємодій. Спосіб, за допомогою якого React обробляє події, трохи відрізняється від JavaScript.

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

Кожен раз, коли ви натискаєте кнопку «плюс», значення лічильника збільшується на 1. Почнемо з частково реалізованого прикладу, який містить все необхідне, крім функцій, пов'язаних з подіями.

<! DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Події</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<style>
#container {
padding: 50px;
background-color: #FFF;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
</script>
</body>
</html>

HTML-документ готовий, і тепер пора додати частково реалізований приклад лічильника. Усередині елемента script під контейнером div вставте наступний код:

class Counter extends React.Component {
render() {
var textStyle = {
fontSize: 72,
fontFamily: "sans-serif",
color: "#333",
fontWeight: "bold"
};
return (
<div style={textStyle}>
{this.props.display}
</div>
);
}
}

class CounterParent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

render() {
var backgroundStyle = {
padding: 50,
backgroundColor: "#FFC53A",
width: 250,
height: 100,
borderRadius: 10,
textAlign: "center"
};
var buttonStyle = {
fontSize: "1em",
width: 30,
height: 30,
fontFamily: "sans-serif",
color: "#333",
fontWeight: "bold",
lineHeight: "3px"
};
return (
<div style={backgroundStyle}>
<Counter display={this.state.count}/>
<button style={buttonStyle}>+</button>
</div>
);
}
}

ReactDOM.render(
<div>
<CounterParent/>
</div>,
document.querySelector("#container")
);

Якщо запустити додаток у браузері ви побачите основу лічильника. Єдине, при натисканні кнопки «плюс», нічого не відбувається.

Створення кнопки дії

Кожен раз, коли ми натискаємо кнопку «плюс», потрібно, щоб значення лічильника збільшилося на 1. Для цього слід вчинити приблизно так:

1. Визначити подію натискання на кнопку.

2. Реалізувати обробник подій і збільшувати значення властивості this.state.count, на яке спирається лічильник.

Почнемо з визначення події click. У React ви визначаєте події, вказавши в JSX-коді. Якщо конкретніше, ви вказуєте в розмітці події, які потрібно прослуховувати, а також і обробники цих подій, які будуть викликані. Внесемо зміни у функцію return в коді компонента CounterParent:

return (
<div style={backgroundStyle}>
<Counter display={this.state.count}/>
<button onClick={this.increase} style={buttonStyle}>+
</button>
</div>
);

Ми інструктували React викликати функцію increase, коли відбувається подія onClick. Тепер реалізуємо функцію increase всередині компонента CounterParent.

class CounterParent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increase = this.increase.bind(this);
}

increase(e) {
this.setState({
count: this.state.count + 1
});
}

Кожен виклик функції increase збільшує значення властивості this.state.count на 1. Оскільки ми маємо справи з подіями, функція increase отримає доступ до будь-яких аргументів події. Ми встановили ці аргументи для доступу до e, як це видно з сигнатури функції increase і в конструкторі ми прив'язуємо значення до функції increase.

Як тільки додаток завантажиться в браузері, натисніть кнопку «плюс», щоб побачити код в дії. Значення лічильника повинно збільшуватися з кожним кліком.

Властивості події

Як відомо, події передають обробникам подій так звані аргументи подій. Вони містять купу властивостей, унікальних для кожного типу подій. У звичайному DOM подія має свій власний тип. Наприклад, якщо ви маєте справу з подією миші, подія і її аргументи мають тип MouseEvent. Цей об'єкт MouseEvent дозволяє вам отримати доступ до даних, що належать до миші, наприклад, про те, яка кнопка була натиснута або в якій позиції екрану. Аргументи подій, пов'язаних з натисканням клавіш на клавіатурі, мають тип KeyboardEvent. Об'єкт KeyboardEvent містить властивості, які дозволяють вам визначити, яка клавіша була натиснута.

Синтетичні події

У React, коли ви вказуєте подію за допомогою JSX-коду, як ми це робили з onClick, ви не звертаєтеся безпосередньо до звичайних подій DOM. Замість цього ви маєте справу із специфічними для React подіями, відомим під назвою SyntheticEvent.

Обробники подій не отримують аргументи звичайної події типу MouseEvent, KeyboardEvent і т.д. Вони завжди отримують аргументи типу SyntheticEvent, які обертають власні події браузера. Кожна подія SyntheticEvent містить наступні властивості:

boolean bubbles

boolean cancelable

DOMEventTarget currentTarget

boolean defaultPrevented

number eventPhase

boolean isTrusted

DOMEvent nativeEvent

void preventDefault ()

boolean isDefaultPrevented ()

void stopPropagation ()

boolean isPropagationStopped ()

DOMEventTarget target

number timeStamp

string type

Ці властивості здаються досить простими. Залежить від того, подія якого типу міститься в обгортці SyntheticEvent. Це означає, що подія SyntheticEvent, обернувшись подією MouseEvent, матиме доступ до характерних для миші властивостей, таких як:

boolean altKey

number button

number buttons

number clientX

number clientY

boolean ctrlKey

boolean getModi erState (key)

boolean metaKey

number pageX

number pageY

DOMEventTarget relatedTarget

number screenX

number screenY

boolean shiftKey

Аналогічно, подія SyntheticEvent, обернувшись подією KeyboardEvent, матиме доступ до цих додаткових властивостей, пов'язаних з клавіатурою.

Робота з властивостями події

Зараз лічильник збільшується на 1 кожен раз, коли ви натискаєте кнопку «плюс». Ми хочемо збільшувати лічильник на 10 при утриманні клавіші Shift на клавіатурі і клацнути по кнопці «плюс» мишею.

Ми можемо це зробити, використовуючи властивість shiftKey, яке існує в події SyntheticEvent при використанні миші:

boolean shiftKey

Спосіб роботи цієї властивості простий. Якщо натиснута клавіша Shift при спрацьовуванні даної події миші, значення властивості shiftKey дорівнює true. В іншому випадку значення властивості shiftKey буде false. Щоб збільшити лічильник на 10 при натисканні клавіші Shift, поверніться до функції increase і внесіть наступні зміни в код:

increase(e) {
var currentCount = this.state.count;
if (e.shiftKey) {
currentCount += 10;
} else {
currentCount += 1;
}
this.setState({
count: currentCount
});
}

Якщо переглянути додаток в браузері, кожен раз, коли ви натискаєте кнопку «плюс», лічильник буде збільшуватися на 1, як і до цього. Якщо ви натиснете кнопку «плюс», утримуючи клавішу Shift, лічильник збільшується на 10.

Дії з подіями

До цього моменту ми розглянули, як працювати з подіями в React, але на поверхневому рівні. Реальні додатки будуть складнішими, і, оскільки React дозволяє досягати результату різними шляхами, вам потрібно буде вивчити деякі додаткові методи, пов'язані з подіями.

Ви не можете напряму прослуховувати події компонентів

Ваш компонент - це не що інше, як кнопка або інший елемент, призначений для інтерфейсу користувача, з яким вони будуть взаємодіяти. Ви не можете зробити щось таке, що зазначено нижче в коді:

increase(e) {

this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<Counter display={this.state.count} />
<PlusButton onClick={this.increase} />//Так робити не можна!!!
</div>
);
}
}

Коли хтось клацає мишею по компоненту PlusButton, викликається функція increase.

class PlusButton extends React.Component {
render() {
return (
<button>
+
</button>
);
}
}

Ви не можете безпосередньо прослуховувати події компонентів. Це пов'язано з тим, що компоненти - це «обгортка» для елементів DOM.

Але ми можемо використовувати обробник подій як властивість і передати його компоненту. Усередині компонента ми можемо потім призначити подію елементу DOM і присвоїти оброблювачу події значення переданої властивості. Розгялнемо все це на прикладі.

class CounterParent extends React.Component {
.
.
.
render() {
return (
<div>
<Counter display={this.state.count} />
<PlusButton clickHandler={this.increase} />
</div>
);
}
}

У цьому прикладі ми створюємо властивість clickHandler, значення якої - обробник події increase. Усередині компонента PlusButton ми можемо написати такий код:

class PlusButton extends React.Component {
render() {
return (
<button onClick={this.props.clickHandler}>
+
</button>
);
}
}

В елементі button ми вказуємо подію onClick і присвоюємо їй значення властивості clickHandler. Під час виконання властивість обробляється як функція increase, а натискання кнопки «плюс» гарантує виклик функції increase.

Прослуховування стандартних подій DOM

Не всі події DOM мають еквівалентні події SyntheticEvent. Для подій, які React офіційно не підтримує, ви повинні слідувати традиційному підходу, застосовуючи слухач addEventListener з декількома додатковими зв'язками для переходу.

Погляньте на наступний фрагмент коду:

class Something extends React.Component {
.
.
.
handleMyEvent(e) {
// Виконання якихось дій
}
componentDidMount() {
window.addEventListener("someEvent", this.handleMyEvent);
}
componentWillUnmount() {
window.removeEventListener("someEvent", this.handleMyEvent);
}
render() {
return (
<div>Привіт!</div>
);
}
}

У нас є компонент Something, який визначає подію з ім'ям someEvent. Ми починаємо обробляти цю подію за методом componentDidMount, який автоматично викликається при візуалізації компонента. Ми відстежуємо подію за допомогою слухача addEventListener і вказуємо як подію, так і обробник подій для виклику.

На що потрібно звернути увагу – видалити слухач подій, якщо компонент буде знищений. Для цього ви можете використовувати протилежність методу componentDidMount - метод componentWillUnmount. Помістіть виклик removeEventListener всередині цього методу, щоб гарантувати відсутність слідів слухача подій після знищення компонента.

Значення this всередині обробника подій

Коли ви маєте справу з подіями в React, значення this в коді обробника подій відрізняється від того, що ви зазвичай бачите в DOM, що не відноситься до React. У цьому випадку значення this всередині обробника події відноситься до елементу, який активувала подія.

function doSomething(e) {
console.log(this); // button element
}
var foo = document.querySelector("button");
foo.addEventListener("click", doSomething, false);

У React значення this не відноситься до елементу, який активувала подія. Ось чому нам потрібно явно вказати зв'язок за допомогою методу bind, як показано в лістингу нижче:

class CounterParent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increase = this.increase.bind(this);
}

increase(e) {
var currentCount = this.state.count;
if (e.shiftKey) {
currentCount += 10;
} else {
currentCount += 1;
}
this.setState({
count: currentCount
});
}

У цьому прикладі значення this всередині обробника події збільшення відноситься до компоненту CounterParent. Воно не відноситься до елементу, який викликав подію. Ви можете вказати таку поведінку, зв'язавши значення this з компонентом всередині конструктора.

24 ЖИТТЄВИЙ ЦИКЛ КОМПОНЕНТА

Методи життєвого циклу - це спеціальні методи, які автоматично викликаються компонентами. Вони повідомляють нам про важливі етапи в житті компонента, і ми можемо використовувати ці повідомлення, щоб звернути увагу або вплинути на те, що повинен зробити компонент.

Давайте розглянемо приклад. Це буде лічильник.

class CounterParent extends React.Component {
constructor(props) {
super(props);
console.log("constructor: Default state time!");
this.state = {
count: 0
};
this.increase = this.increase.bind(this);
}
increase() {
this.setState({
count: this.state.count + 1
});
}
componentWillUpdate(newProps, newState) {
console.log("componentWillUpdate: Component is about to update!");
}
componentDidUpdate(currentProps, currentState) {
console.log("componentDidUpdate: Component just updated!");
}
componentWillMount() {
console.log("componentWillMount: Component is about to mount!");
}
componentDidMount() {
console.log("componentDidMount: Component just mounted!");
}
componentWillUnmount() {
console.log("componentWillUnmount: Component is about to be removed from the DOM!");
}
shouldComponentUpdate(newProps, newState) {
console.log("shouldComponentUpdate: Should component update?");
if (newState.count < 5) {
console.log("shouldComponentUpdate: Component should update!");
return true;
} else {
ReactDOM.unmountComponentAtNode(destination);
console.log("shouldComponentUpdate: Component should not update!");
return false;
}
}
componentWillReceiveProps(newProps) {
console.log("componentWillReceiveProps: Component will get new props!");
}
render() {
var backgroundStyle = {
padding: 50,
border: "#333 2px dotted",
width: 250,
height: 100,
borderRadius: 10,
textAlign: "center"
};
return (
<div style={backgroundStyle}>
<Counter display={this.state.count} />
<button onClick={this.increase}>
+
</button>
</div>
);
}
}
console.log("defaultProps: Default prop time!");
CounterParent.defaultProps = {
};

Даний приклад – це список методів життєвого циклу, визначених за допомогою інструкцій console.log. Якщо запустити додаток і перейти в консоль браузера, можна помітити порядок викликів методів життєвого циклу.

Етап початкового рендеринга

Коли компонент створюється і поміщається в DOM, викликаються наступні методи життєвого циклу:

Отримання властивостей за замовчуванням

Ця властивість компонента дозволяє вказати дефолтне значення this.props. Якщо ми хочемо налаштувати властивість name компонента CounterParent, код буде виглядати наступним чином:

CounterParent.defaultProps = {
name: "Термінатор"
};

Цей код виконується до того, як компонент буде створений, або в нього будуть передані властивості з батьківських компонентів.

Отримання стану за замовчуванням

Цей крок виконується всередині конструктора компонента. Ви можете вказати дефолтне значення this.state в частині створення компонента:

constructor(props) {
super(props);
console.log("constructor: Default state time!");
this.state = {
count: 0
};
this.increase = this.increase.bind(this);
}

Зверніть увагу, як ми визначаємо об'єкт state і ініціалізуємо його за допомогою властивості count, значення якого дорівнює 0.

componentWillMount

Це останній метод, який викликається до того, як компонент буде переданий в DOM. Тут важливо відзначити: якщо ви викликаєте метод setState всередині методу componentWillMount, компонент не буде повторно рендеритися.

render

Цей метод вам дуже добре знайомий. Кожен компонент повинен мати цей метод, так як він відповідає за повернення JSX-коду. Якщо не потрібно нічого рендерити (візуалізувати), поверніть значенняnull або false.

componentDidMount

Цей метод викликається відразу після того, як компонент візуалізовано і поміщено в DOM. На цьому етапі ви можете безпечно виконувати будь-які операції запиту DOM, не турбуючись про готовність компонента. Якщо у вас є код, залежний від готовності компонента, ви можете вказати його тут.

За винятком методу render, всі перераховані методи життєвого циклу можуть спрацьовувати тільки один раз.

Етап оновлення

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

Зміна станів

Коли відбувається зміна стану, компонент знову викликає свій метод render. Будь-які компоненти, які покладаються на виведення цього компонента, також викликають методи render. Це робиться для того, щоб компонент завжди відображав саму останню версію. Коли відбувається зміна стану, викликаються всі методи життєвого циклу, описані нижче:

shouldComponentUpdate

Іноді не потрібно, щоб компонент оновлювався при зміні стану. Метод shouldComponentUpdate дозволяє контролювати, чи виконувати оновлення. Якщо ви використовуєте цей метод зі значенням true, компонент буде оновлюватися. Якщо цей метод повертає значення false, цей компонент пропускає оновлення. Розглянемо приклад.

shouldComponentUpdate(newProps, newState) {
console.log("shouldComponentUpdate: Should component update?");
if (newState.count < 5) {
console.log("shouldComponentUpdate: Component should update!");
return true;
} else {
ReactDOM.unmountComponentAtNode(destination);
console.log("shouldComponentUpdate: Component should not update!");
return false;
}
}

Цей метод викликається з двома аргументами з іменами newProps і newState. У цьому фрагменті коду перевіряється, чи значення властивості state менше або дорівнює 5. Якщо значення менше або дорівнює 5, повертається значення true, тобто компонент повинен оновлюватися.

Якщо значення більше 5, повертається значення false – компонент не повинен оновлюватися.

componentWillUpdate

Цей метод викликається безпосередньо перед оновленням компонента. Слід зазначити, що ви не можете змінити стан, викликавши this.setState з цього методу.

render

Якщо ви не налаштували поведінку при оновленні за допомогою методу shouldComponentUpdate, код методу render знову викликається, щоб переконатися, що компонент відображається коректно.

componentDidUpdate

Цей метод викликається після поновлення компонента і після виклику методу render.

Зміни властивостей

В іншій ситуації оновлення компонента, значення його властивості змінюється після того, як воно було візуалізовано в DOM. В цьому випадку виклики успадковують методи життєвого циклу, як показано нижче:

Єдиний новий метод - componentWillReceiveProps. Цей метод отримує один аргумент, який містить нове значення властивості для присвоювання.

Етап розмонтування

Останній етап, на який потрібно звернути увагу, - це коли компонент знищується і видаляється з DOM

Тут активний лише один метод життєвого циклу, який називається componentWillUnmount. На цьому етапі виконуються завдання, пов'язані з очищенням, такі як видалення слухачів подій і зупинка таймерів.

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


© 2006—2023 СумДУ