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

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

Доступ до елементів DOM в React. Доступ до зовнішніх даних

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


25 ДОСТУП ДО ЕЛЕМЕНТІВ DOM В REACT

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

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

Зверніть увагу на текстове поле та кнопку OK. Кнопка отримує фокус, а вказане значення кольору залишається в полі.

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

Наприклад, ми могли б скинути введене значення кольору та повернути фокус у текстове поле одразу після натискання кнопки OK. Це означало б, що якби ми вказали значення фіолетового кольору і натиснули кнопку OK, то в результаті побачили б щось наступне:

Введене значення фіолетового кольору скинуто, а фокус повернувся до текстового поля. Правильно реалізувати таку поведінку з використанням JSX та традиційних методів React складно. З іншого боку, реалізувати таку поведінку за допомогою JavaScript API DOM для різних HTML-елементів досить просто. Для цього ми скористаємося так званими посиланнями (refs), доступними в React, щоб отримати доступ до API DOM для 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>
<style>
#container {
padding: 50px;
background-color: #FFF;
}

.colorSquare {
box-shadow: 0px 0px 25px 0px #333;
width: 242px;
height: 242px;
margin-bottom: 15px;
}

.colorArea input {
padding: 10px;
font-size: 16px;
border: 2px solid #CCC;
}

.colorArea button {
padding: 10px;
font-size: 16px;
margin: 10px;
background-color: #666;
color: #FFF;
border: 2px solid #666;
}

.colorArea button:hover {
background-color: #111;
border-color: #111;
cursor: pointer;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
class Colorizer extends React.Component {
constructor(props) {
super(props);
this.state = {
color: "",
bgColor: "white"
};
this.colorValue = this.colorValue.bind(this);
this.setNewColor = this.setNewColor.bind(this);
}

colorValue(e) {
this.setState({
color: e.target.value
});
}

setNewColor(e) {
this.setState({
bgColor: this.state.color
});
e.preventDefault();
}

render() {
var squareStyle = {
backgroundColor: this.state.bgColor
};
return (
<div className="colorArea">
<div style={squareStyle} className="colorSquare"></div>
<form onSubmit={this.setNewColor}>
<input onChange={this.colorValue}
placeholder="Значення кольору"></input>
<button type="submit">ok</button>
</form>
</div>
);
}
}

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

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

Знайомство з посиланнями

Як ви вже знаєте, у різних методах візуалізації ми пишемо HTML-подібний JSX-код. JSX-код — це опис того, як має виглядати DOM. Він не є фактичним HTML-кодом, незважаючи на те, що він дуже схожий на нього. Щоб забезпечити зв'язок між JSX-кодом та кінцевими HTML-елементами в DOM, React надає нам посилання (refs).

Розглянемо метод render у коді палітри:

render() {
var squareStyle = {
backgroundColor: this.state.bgColor
};
return (
<div className="colorArea">
<div style={squareStyle} className="colorSquare"></div>
<form onSubmit={this.setNewColor}>
<input onChange={this.colorValue}
placeholder="Значення кольору"></input>
<button type="submit">ok</button>
</form>
</div>
);
}

Всередині цього методу render ми повертаємо великий фрагмент JSX , що представляє (серед іншого) елемент input, якому передаємо значення кольору. Ми хочемо отримати доступ до елементів введення у представленні DOM, щоб ми могли викликати деякі API за допомогою JavaScript. Спосіб застосування посилань полягає у встановленні атрибуту ref елементу, на який ми хочемо послатися:

render() {
var squareStyle = {
backgroundColor: this.state.bgColor
};
return (
<div className="colorArea">
<div style={squareStyle} className="colorSquare"></div>
<form onSubmit={this.setNewColor}>
<input onChange={this.colorValue}
ref={}
placeholder="Значення кольору"></input>
<button type="submit">ok</button>
</form>
</div>
);
}

Оскільки нас цікавить елемент введення, до нього прив'язаний атрибут ref . На даному етапі атрибут ref порожній. Зазвичай в атрибут ref необхідно передавати функцію зворотного виклику JavaScript. Ця функція викликається автоматично, коли монтується компонент, що містить цей метод render. Якщо ми встановимо значення атрибута ref функцію JavaScript, яка зберігає посилання на елемент DOM, код буде виглядати приблизно так:

render() {
var squareStyle = {
backgroundColor: this.state.bgColor
};

var self = this;

return (
<div className="colorArea">
<div style={squareStyle} className="colorSquare"></div>
<form onSubmit={this.setNewColor}>
<input onChange={this.colorValue}
ref={
function(el) {
self._input = el;
}
}
placeholder="Значення кольору"></input>
<button type="submit">ok</button>
</form>
</div>
);
}

Результат роботи цього коду, який виконується після монтування компонентів, простий: ми можемо отримати доступ до HTML, що представляє елемент input, з будь-якої позиції нашого компонента, використовуючи метод self.input.

Функція зворотного виклику виглядає так:

function (el) {
self._input = el;
}

Ця анонімна функція викликається при монтуванні компонента, а посилання на кінцевий HTML-елемент DOM передається як аргумент. Ми перехоплюємо цей аргумент за допомогою дентифікатора el . Тіло функції зворотного виклику встановлює властивість користувача, _input, як значення елемента DOM. Щоб гарантувати, що ми створимо цю властивість на нашому компоненті, ми використовуємо змінну self для створення замикання — це відноситься до нашого компоненту, а не до самої функції зворотного виклику.

Тепер маючи доступ до елемента input ми можемо очистити вміст елемента введення та сфокусуватися на ньому після відправки форми. Код буде працювати в методі setNewColor:

setNewColor(e) {
this.setState({
bgColor: this.state.color
});

this._input.focus();
this._input.value = "";

e.preventDefault();
}

Виклик методу this._input.value="" очищає введене значення. Ми фокусуємось на елементі введення, викликавши функцію this._input.focus(). Потім ми можемо викликати властивість value та метод focus, які API DOM надає для цього елемента.

Примітка. Спрощення за допомогою стрілочних функцій ES6

Для взаємозв'язку ми створили ініціалізовану змінну self, щоб переконатися, що ми створили властивість _input компонента. Використовуючи стрілочні функції, можна спростити код так:

ref={
(el) => this._input = el
}

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

До цих пір ми працювали з HTML тільки в контексті того, що генерує JSX-код або з одного чи декількох компонентів. Це означає, що ми обмежені ієрархією DOM, яку надають батьківські компоненти. Тому наявність довільного доступу до будь-якого елемента DOM у будь-якому місці сторінки начебто неможлива. Як виявилось, ви можете відображати JSX-контент для будь-якого елемента DOM у будь-якому місці сторінки; ви не обмежені надсиланням свого JSX-коду в батьківський компонент. Цей функціонал, відомий як портали.

Те, як ми використовуємо портал, дуже схоже на те, що ми робимо з методом ReactDOM.render. Ми вказуємо JSX-код, який хочемо візуалізувати, і вказуємо елемент DOM, який ми також хочемо візуалізувати.

Щоб побачити все це у дії, поверніться до нашого прикладу та додайте наступний елемент h1 трохи вище за визначення елемента div з ідентифікатором container

<body>

<h1 id="colorHeading">Палітра</h1>

<div id="container"></div>

Потім додайте наступне правило стилю елемент style:

#colorHeading {
padding: 0;
margin: 50px;
margin-bottom: –20px;
font-family: sans-serif;
}

Ми хочемо змінити значення елемента h1, щоб показати ім'я кольору, який відображається в даний момент. Слід підкреслити, що елемент h1 є «родичем» елемента divcontainer, де наша програма налаштована на візуалізацію.

Щоб виконати необхідні дії, поверніться до методу render компонента Colorizer і додайте наступний рядок:

render() {
var squareStyle = {
backgroundColor: this.state.bgColor
};

return (
<div className="colorArea">
<div style={squareStyle} className="colorSquare"></div>
<form onSubmit={this.setNewColor}>
<input onChange={this.colorValue}
ref={
(el) => this._input = el
}
placeholder="Значення кольору"></input>
<button type="submit">ok</button>
</form>
<ColorLabel color={this.state.bgColor}/>
</div>
);
}

Ми створюємо компонент ColorLabel і оголошуємо властивість з ім'ям color з його значенням, заданим як bgColor state.

Створимо компонент ColorLabel:

var heading = document.querySelector("#colorHeading");

class ColorLabel extends React.Component {
render() {
return ReactDOM.createPortal(
": " + this.props.color,
heading
);
}
}

Ми посилаємось на елемент h1 за допомогою змінної heading. Зверніть увагу на те, як виглядає інструкція return. Ми повертаємо результат виклику методу ReactDOM.createPortal():

return ReactDOM.createPortal()

Метод ReactDOM.createPortal() приймає два аргументи: JSX для виведення та елемент DOM для виведення даного JSX-контенту. JSX-контент, який ми виводимо, — це лише деякі символи форматування та значення кольору, яке ми передали як властивість:

": " + this.props.color,

Елемент DOM, який ми виводимо, це елемент h1, на який посилається змінна heading:

heading

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

Використовуючи портали, ми маємо прямий доступ до будь-якого елемента DOM сторінки і можемо відображати в ньому контент, минаючи традиційну ієрархію «предок/нащадок», якою користувалися досі.

26 НАЛАШТУВАННЯ СЕРЕДОВИЩА РОЗРОБКИ REACT

Досі ми збирали програми React, включаючи кілька файлів сценаріїв:

<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>

Ці файли сценаріїв завантажували не лише бібліотеки React, але й компілятор Babel, що допомагає браузеру обробляти JSX код:

Як згадувалося раніше, у цьому підході недоліком є продуктивність. Оскільки браузер обробляє всі сторінки, що завантажуються, як це зазвичай і буває, він також несе відповідальність за перетворення JSX-коду в JavaScript-код. Це перетворення - трудомісткий процес, який є прийнятним під час розробки, але не підходить під час користування, якщо кожен клієнт програми повинен «відплатити» за це зниженням продуктивності.

Рішення полягає в тому, щоб налаштувати середовище розробки так, щоб перетворення JSX - JS оброблялося як частина створення програми:

Тепер браузер завантажує програму та звертається до перетвореного (і оптимізованого) файлу JavaScript. Давайте розглянемо один із способів налаштування середовища розробки з використанням комбінації Node js, Babel та webpack, яке створене Facebook, що спрощує процес розробки.

Проект Create React

Створений компанією Facebook проект Create React (github.com/facebookincubator/create-react-app) – значно спростив процес налаштування середовища розробки React. Ви запускаєте кілька команд в оболонці командного рядка і без вашої участі створюється проект React із усіма необхідними конфігураціями.

Перш ніж розпочати роботу, переконайтеся, що встановлена остання версія Node.js (nodejs.org). Потім відкрийте оболонку командного рядка. Перше, що потрібно зробити, це створити проект Create React. Введіть наступну команду в командному рядку та натисніть клавішу Enter/Return

npm install -g create-react-app helloworld

Процес може зайняти від кількох секунд до кількох хвилин.

Коли команда буде повністю виконана, у вас з'явиться проект Helloworld. Перейдіть до папки Helloworld створеного проекту, ввівши таку команду:

cd helloworld

У цій папці введіть наступну команду, щоб перевірити програму:

npm start

Якщо у вас встановлено менеджер yarn, Create віддасть перевагу використанню його замість npm для встановлення, і ви побачите екранні інструкції, в яких говориться, що замість команди npm start слід виконувати команду yarn start.

Проект буде зібраний, буде запущено локальний веб-сервер, і ви побачите запущений проект, як показано на наступному рисунку:

Розглянемо, що саме було створено. Структура папок та файлів після виконання команди create-react-app helloworld буде виглядати так, як показано на рисунку нижче:

Файл Index.html у папці public завантажується у браузер.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>

</body>
</html>

На що потрібно звернути увагу, це елемент div з ідентифікатором root. Тут буде виводитись контент програми React.

Початковий файл програми React носить ім'я index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Зверніть увагу на виклик ReactDOM.render, який шукає елемент root, що викликається із файлу index.html. Ви також побачите групу інструкцій import у верхній частині коду сторінки. Ці інструкції імпорту є частиною JavaScript-коду, який називають модулями. Мета модулів - розділити функціональність програми на дрібніші частини. Деякі з модулів, які ви імпортуєте, належать до коду проекту. Інші, такі як React та ReactDOM, знаходяться поза проектом, але також можуть бути імпортовані.

У коді ми імпортуємо бібліотеки React та React-DOM. Ми також імпортуємо файл CSS, службовий сценарій, на який ми будемо посилатися на ім'я registerServiceWorker, та компонент React з ім'ям App.

Файл App.js:

import logo from './logo.svg';
import './App.css';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;

Зауважте, що файл App.js містить власні інструкції import. Деякі з них, наприклад, бібліотеки React і Component, необхідні з огляду на призначення коду. Тут цікавий останній рядок: export default app.

Він містить команду export та ім'я, яке наш проект буде використовувати для ідентифікації модуля, що експортується. Ви будете використовувати це ім'я під час імпорту модуля App в інші частини проекту, наприклад, у файлі index.js. Також імпортуються зображення та файли CSS, необхідні для коректного відображення цієї сторінки. Ці модулі, інструкції import та export — забезпечують, щоб код програми став більш керованим. Замість того, щоб визначати все в одному гігантському файлі, ви можете розбити свій код і пов'язані з ним ресурси на кілька файлів. Залежно від того, на які файли ви посилаєтеся та які файли завантажуються перед іншими файлами, все це може оптимізувати кінцевий результат.

Створення демонстраційної програми

На екрані потрібно відобразити слова «Привіт, світ!». Ми розглянемо процес, створивши компонент HelloWorld. Увагу слід звернути на структурування файлів у проекті, щоб забезпечити правильне створення програми.

Для початку перейдіть до каталогу src і видаліть всі файли, які є там. Потім створіть файл index.js. У цей файл додайте наступний код:

import React from "react";
import ReactDOM from "react-dom";
import HelloWorld from "./HelloWorld";
ReactDOM.render(
<HelloWorld/>,
document.getElementById("root")
);

У тому ж каталозі src, в якому ми зараз знаходимося, створіть файл HelloWorld.js. Потім відкрийте його та змініть, додавши наступний код:

import React, {Component} from "react";
class HelloWorld extends Component {
render() {
return (
<div className="helloContainer">
<h1>Привіт, світ!</h1>
</div>
);
}
}
export default HelloWorld;

Запустивши додаток, можна побачити наступний результат:

Створіть таблицю стилів з ім'ям index.css, і додайте до файлу такі правила стилів:

body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}

Для того, щоб посилатися на створений документ index.css у файлі index.js потрібно додати до нього виділену в лістингу інструкцію import:

import React from "react";
import ReactDOM from "react-dom";
import HelloWorld from "./HelloWorld";
import "./index.css";

ReactDOM.render(
<HelloWorld/>,
document.getElementById("root")
);

Якщо ви повернетесь до браузера, ви помітите, що сторінка автоматично оновилася з усіма останніми змінами. Ви побачите слова "Привіт, світ!", розташовані в центрі по вертикалі та горизонталі.

Примінимо тепер стиль до тексту. Ми могли б додати відповідні правила стилю до файлу index.css, але більш слушним рішенням було б створення нового файлу CSS, в якому ми зішлемося тільки на компонент HelloWorld.

Створіть файл HelloWorld.css у папці src. Додайте до нього такі правила стилів:

h1 {
font-family: sans-serif;
font-size: 56px;
padding: 5px;
padding-left: 15px;
padding-right: 15px;
margin: 0;
background: linear-gradient(to bottom,
white 0%,
white 62%,
gold 62%,
gold 100%);
}

Залишилося лише зіслатися на цю таблицю стилів у файлі HelloWorld.js:

import "./HelloWorld.css";

27 РОБОТА ІЗ ЗОВНІШНІМИ ДАНИМИ У REACT

Робота із зовнішніми даними сьогодні досить звичайна процедура у веб-додатках. Процес зазвичай виглядає так:

  1. 1. Програма надсилає запит для отримання певних даних на віддалений сервер.
  2. 2. Віддалений сервер отримує запит і надсилає назад необхідні дані.
  3. 3. Програма отримує дані.
  4. 4. Додаток обробляє дані та відображає їх користувачеві.

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

Все це відбувається без необхідності оновлення сторінки або втрати стану, де знаходиться сторінка.

Основи веб-запитів

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

Протокол HTTP представляє спільну мову, яка дозволяє браузеру та іншим програмам та пристроям спілкуватися з усіма серверами, розташованими в Інтернеті. Запити, які браузер надсилає від вашого імені за допомогою протоколу HTTP, відомі як HTTP-запити, і ці запити виходять за рамки простого завантаження нової сторінки під час навігації. Загалом в більшості випадків відбувається оновлення існуючої сторінки за допомогою даних, отриманих в результаті HTTP-запиту.

Щоб отримати інформацію, наприклад, про користувача, потрібно зробити HTTP-запит:

GET /user

Accept: application/json

На цей запит сервер може повернути наступну відповідь:

200 OK

Content-Type: application/json

{

"name": "Ivan Ivanov",

"url": "http: https://www.ivanov.com"

}

Подібний обмін відбувається кілька разів, і все це повністю підтримується у JavaScript. Ця технологія асинхронно запитувати та обробляти дані з сервера, не вимагаючи навігації/перезавантаження сторінки, має назву: Ajax. Абревіатура розшифровується як асинхронний JavaScript та XML.

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

У JavaScript об'єкт, який відповідає за відправлення та отримання HTTP-запитів називається XMLHttpRequest. Цей об'єкт дозволяє виконувати кілька операцій, важливих для створення веб-запитів:

  1. 1. Надсилання запиту на сервер.
  2. 2. Перевірка статусу запиту.
  3. 3. Отримання та аналіз відповіді на запит.
  4. 4. Визначення події readystatechange, яка допомагає відреагувати на статус запиту.

Розглянемо роботу обміну із зовнішніми даними на прикладі додатку.

Після створення нового проекту командою

create-react-app ipaddress

у папці public створіть файл index.html. Додайте до нього наступний вміст:

<! DOCTYPE html>
<html>
<head>
<title>IP-адреса</title>
</head>
<body>
<div id="container">
</div>
</body>
</html>

У цьому коді створюється елемент div з назвою container. Потім перейдіть до папки src і створіть файл index.js. У цьому файлі додайте наступний код:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import IPAddressContainer from "./IPAddressContainer";

var destination = document.querySelector("#container");

ReactDOM.render(
<div>
<IPAddressContainer/>
</div>,
destination
);

Це вхідна точка для програми. Код містить посилання на шаблони React, ReactDOM, неіснуючий файл CSS і неіснуючий компонент IPAddressContainer. Також вказано виклик ReactDOM.render , який відповідає за запис нашого контенту в елемент div з ім'ям container, який ми визначили в HTML-файлі.

Тепер у папці src створіть файл index.css і додайте до нього таке правило стилю:

body {
background-color: #FFCC00;
}

Отримання IP-адреси

Далі нам потрібно створити компонент, завданням якого є отримання IP-адреси від веб-сервісу, збереження його у вигляді стану, а потім надання цього стану як властивості будь-якому компоненту, який його зажадає. Давайте створимо необхідний компонент. У папці src створіть файл з ім'ям IPAddressContainer.js, а потім додайте до нього наступні рядки коду:

import React, {Component} from "react";

class IPAddressContainer extends Component {
render() {
return (
<p>Контент!</p>
);
}
}
export default IPAddressContainer;

Створимо HTTP-запит:

import React, {Component} from "react";

var xhr;

class IPAddressContainer extends Component {

constructor(props) {
super(props);
this.state = {
ip_address: "..."
};

this.processRequest = this.processRequest.bind(this);
}

componentDidMount() {
xhr = new XMLHttpRequest();
xhr.open("GET", "https://ipinfo.io/json", true);
xhr.send();
xhr.addEventListener("readystatechange", this.processRequest, false);
}

processRequest() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
this.setState({
ip_address: response.ip
});
}
}

render() {
return (
<p>Контент!</p>
);
}
}

export default IPAddressContainer;

Коли компонент стає активним і викликається метод життєвого циклу componentDidMount, ми створюємо HTTP-запит і надсилаємо його на веб-сервіс ipinfo.io:

componentDidMount() {

xhr = new XMLHttpRequest();
xhr.open("GET", "https://ipinfo.io/json", true);
xhr.send();
xhr.addEventListener("readystatechange", this.processRequest, false);
}

Коли ми отримуємо відповідь від служби ipinfo.io, викликається функція processRequest:

processRequest() {

if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
this.setState({
ip_address: response.ip
});
}
}

Змініть виклик render на IP-адресу, збережену в стані:

render() {
return (
<p>{this.state.ip_address}</p>
);
}

Тепер відформатуємо результат. Для цього ми не будемо додавати HTML-елементи та інші деталі стилізації до методу render компонента IPAddressContainer.

Натомість створимо новий компонент, у якого одна мета — форматування виводу.

Створіть файл з ім'ям IPAddress.js у папці src. Потім відредагуйте його, додавши до нього такий вміст:

import React, {Component} from "react";

class IPAddress extends Component {
render() {
return (
<div>Привіт!</div>
);
}
}
export default IPAddress;

Тут ми визначаємо новий компонент з ім'ям IPAddress, який буде відповідати за відображення додаткового тексту та забезпечення того, щоб IP-адреса була відформатована.

Спочатку потрібно змінити код методу render цього компонента так:

import React, {Component} from "react";

class IPAddress extends Component {
render() {
return (
<div>
<h1>{this.props.ip}</h1>
<p>(Це ваша IP-адреса)</p>
</div>
);
}
}
export default IPAddress;

Щоб відформатувати ці елементи, створіть у папці src файл CSS з ім'ям IPAddress.css. У цьому файлі вкажіть такі правила стилю:

h1 {
font-family: sans-serif;
text-align: center;
padding-top: 140px;
font-size: 60px;
margin: 15px;
}
p {
font-family: sans-serif;
color: #907400;
text-align: center;
}

Визначивши стилі, нам потрібно послатись на цей CSS-файл у файлі IPAddress.js.

import "./IPAddress.css";

Залишилося тільки використати компонент IPAddress і передати IP-адресу. Перший крок – переконатися, що компонент IPAddressContainer знає про компонент IPAddress, посилаючись на нього.

Другий крок – змінити код методу render наступним чином:

render() {
return (
<IPAddress ip={this.state.ip_address}/>
);
}

 

28 СТВОРЕННЯ ОДНОСТОРІНКОВОГО ДОДАТКУ

REACT ЗА ДОПОМОГОЮ РОУТЕРА

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

Коли користувачі переміщаються вашим додатком, вони мають деякі очікування:

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

2. Користувачі очікують, що зможуть успішно використовувати кнопки браузера «назад» та «вперед».

3. Користувачі повинні мати можливість перейти до певного в’ю безпосередньо, використовуючи відповідну URL-адресу.

З багатосторінковими додатками ці речі реалізовані легко. Вам навіть не доведеться нічого робити. З односторінковим додатком все складніше. Ви повинні переконатися, що за навігацію у програмі відповідають відповідні URL-адреси. Ви повинні переконатися, що історія в браузері синхронізується з кожним переходом, щоб користувачі могли використовувати кнопки "назад" та "вперед". Якщо користувачам потрібно зробити закладку на певне в’ю програми або копіювати/вставити URL-адресу для доступу до цього представлення пізніше, ви повинні переконатися, що ваша односторінкова програма коректно працює і в цьому напрямку. Щоб вирішити ці проблеми, існують методи, відомі як маршрутизація. При маршрутизації ви зіставляєте URL-адреси з об'єктами, які не є фізичними сторінками, такими як окремі в’ю в односторінковому додатку.

Це звучить складно, але, на щастя, багато бібліотек JavaScript може допомогти вам у цьому. Одна з таких бібліотек JavaScript називається React Router (github.com/reactjs/react-router). React Router надає можливості маршрутизації для односторінкових програм, створених у React. Що радує, це те, що ця бібліотека враховує все, що ви вже знаєте про React, і надає вам зручні інструменти для роботи з маршрутизацією.

 

Приклад.

Спочатку розглянемо просту програму React, в якій використовується бібліотека React Router , яка забезпечує всі переваги навігації та завантаження представлень: перехід по пунктах меню, щоб завантажити відповідний розділ сайту, робота кнопок «назад» і «вперед», щоб переконатися, що вони працюють.

Спершу нам потрібно налаштувати проект. Для цього ми скористаємось командою create-react-app. Перейдіть до папки, в якій потрібно створити програму, і введіть наступну команду в командний рядок:

create-react-app react_spa

Команда створить у папці новий проект із ім'ям react_spa. Перейдіть до цієї папки:

cd react_spa

Тепер потрібно встановити бібліотеку React Router. Для цього виконайте таку команду:

npm i react-router-dom –save

Команда скопіює необхідні файли React Router і зареєструє їх у файлі package.json, щоб програма була в курсі існування бібліотеки React Router.

У папці react_spa видаліть вміст папок public і src . Тепер створимо файл index.html, який буде відправною точкою нашої програми. У папці public створіть файл index.html та додайте у нього наступний вміст:

<! DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>React Router Example</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

Тепер створимо JavaScript сценарій. У папці src створіть файл index.js і додайте до нього такий вміст:

import React from "react";
import ReactDOM from "react-dom";
import Main from "./Main";

ReactDOM.render(
    <Main/>,
    document.getElementById("root")
);

У коді вказано виклик методу ReactDOM.render, а також команда рендерингу компонента Main… якого ще немає. Компонент Main стане відправною точкою в нашому односторінковому додатку з React Route.

 

Створення односторінкового додатку

Односторінковий додаток має основний батьківський компонент. Кожна окрема сторінка програми буде окремим компонентом, який переадресовується в головний компонент. Бібліотека React Router надає можливість вибору компонентів, які потрібно показати або приховати. Навігація пов'язана з адресним рядком браузера та кнопками «назад» та «вперед».

 

Відображення початкового фрейму

При створенні односторінкового додатка частина його сторінки завжди залишається статичною. Ця статична частина, яка називається фреймом програми, може бути одним невидимим елементом HTML, який діє як контейнер для всього контенту, або може включати деякі додаткові видимі об'єкти, такі як header, footer або навігація. У нашому випадку фрейм програми буде компонентом, який містить елементи інтерфейсу користувача для header з навігацією і порожню область для завантажуваного вмісту. У папці src створіть файл Main.js і додайте до нього такий вміст:

class Main extends Component {
    render() {
        return (
            <div>
                <h1>Простий SPA-додаток</h1>
                <ul className="header">
                    <li><a href="/">Головна</a></li>
                    <li><a href="/stuff">Продукти</a></li>
                    <li><a href="/contact">Контакти</a></li>
                </ul>
                <div className="content">
                </div>
            </div>
        );
    }
}

export default Main;

Погляньте на код. Ми маємо компонент Main, який повертає деякий HTML-контент. Щоб переглянути результат у браузері, виконайте npm start.

Ви повинні побачити невідформатовану версію назви програми та кілька елементів списку:

 

Створення сторінок із контентом

Наша програма буде мати три сторінки з контентом. Цей контент буде простим компонентом, який виводить певний JSX-код.

Спочатку створіть файл з ім'ям Home.js у каталозі src і додайте до нього наступний вміст:

import React, {Component} from "react";

class Home extends Component {
    render() {
        return (
            <div>
                <h2>HELLO</h2>
                <p>Cras facilisis urna ornare ex volutpat, et
                    convallis erat elementum. Ut aliquam, ipsum vitae
                    gravida suscipit, metus dui bibendum est, eget
                    rhoncus nibh metus nec massa. Maecenas hendrerit
                    laoreet augue nec molestie. Cum sociis natoque
                    penatibus et magnis dis parturient montes,
                    nascetur ridiculus mus.</p>
                <p>Duis a turpis sed lacus dapibus elementum sed eu
                    lectus.</p>
            </div>
        );
    }
}
export default Home;

Потім створіть файл Stuff.js у тому ж каталозі та додайте до нього наступний код:

import React, {Component} from "react";

class Stuff extends Component {
    render() {
        return (
            <div>
                <h2>Продукти</h2>
                <p>Mauris sem velit, vehicula eget sodales vitae,
                    rhoncus eget sapien:</p>
                <ol>
                    <li>Nulla pulvinar diam</li>
                    <li>Facilisis bibendum</li>
                    <li>Vestibulum vulputate</li>
                    <li>Eget erat</li>
                    <li>Id porttitor</li>
                </ol>
            </div>
        );
    }
}
export default Stuff;

Також створіть файл з ім'ям Contact.js у папці src і додайте до нього наступний код:

import React, {Component} from "react";
class Contact extends Component {
    render() {
        return (
            <div>
                <h2>Є питання?</h2>
                <p>Опублікуйте свої питання на  <a href="http://forum.kirupa.com">форумі</a>.
                </p>
            </div>
        );
    }
}
export default Contact;

 

Використання бібліотеки React Router

У нас є фрейм додатку у формі компонента Main. Також є сторінки з контентом, представлені компонентами Home, Stuff і Contact. Тепер нам потрібно пов'язати все це разом, щоб створити односторінковий додаток. Для цього будемо використовувати бібліотеку React Router. Для початку повернемося до файлу Main.js і переконаймося, що інструкції import у ньому виглядають наступним чином:

import React, {Component} from "react";
import {
    BrowserRouter as Router,
    Routes,
    Route,
    Link
} from "react-router-dom";
import Home from "./Home";
import Stuff from "./Stuff";
import Contact from "./Contact";

Ми імпортуємо компоненти Route, NavLink та HashRouter з NPM-пакета react-router-dom, встановленого раніше. Крім того, ми імпортуємо компоненти Home, Stuff і Contact, тому що будемо посилатися на них як на частину контенту, що завантажується.

Бібліотека React Router у процесі роботи визначає так звану область маршрутизації:

1. Посилання для навігації.

2. Контейнер для завантаження вмісту.

Існує тісний взаємозв'язок між URL-адресами навігаційних посилань та контентом, який у результаті завантажується.

Перше, що потрібно зробити, це визначити область маршрутизації. У методі render компонента Main внесіть такі зміни у код:

import React, {Component} from "react";
import {
    BrowserRouter as Router,
    Routes,
    Route,
    Link
} from "react-router-dom";
import Home from "./Home";
import Stuff from "./Stuff";
import Contact from "./Contact";

class Main extends Component {
    render() {
        return (
            <Router>
                <div>
                    <h1>Простий SPA-додаток</h1>
                    <ul className="header">
                        <li><Link to="/">Головна</Link></li>
                        <li><Link to="/stuff">Продукти</Link></li>
                        <li><Link to="/contact">Контакти</Link></li>
                    </ul>
                    <div className="content">
 
                    </div>
                </div>
            </Router>


        );
    }
}

export default Main;

Компонент BrowserRouter створює основу для навігації та обробки історії браузера, з якої складається маршрутизація. Потім нам слід визначити навігаційні посилання. Ми вже маємо елементи списку. Нам потрібно замінити їх більш спеціалізованим компонентом Link:

import React, {Component} from "react";
import {
    BrowserRouter as Router,
    Routes,
    Route,
    Link
} from "react-router-dom";
import Home from "./Home";
import Stuff from "./Stuff";
import Contact from "./Contact";

class Main extends Component {
    render() {
        return (
            <Router>
                <div>
                    <h1>Простий SPA-додаток</h1>
                    <ul className="header">
                        <li><Link to="/">Головна</Link></li>
                        <li><Link to="/stuff">Продукти</Link></li>
                        <li><Link to="/contact">Контакти</Link></li>
                    </ul>
                    <div className="content">
                        <Routes>
                            <Route exact path="/" element={<Home/>}/>
                            <Route path="/stuff" element={<Stuff/>}/>
                            <Route path="/contact" element={<Contact/>}/>
                        </Routes>
                    </div>
                </div>
            </Router>


        );
    }
}

export default Main;

 

Зверніть увагу на URL-адресу кожного посилання, яке ми повідомляємо роутеру для навігації. Це значення URL-адреси (визначається властивістю) діє як ідентифікатор, який гарантує завантаження правильного вмісту. Ми зіставляємо URL-адреси з контентом за допомогою компонента Route.

import React, {Component} from "react";
import {
    BrowserRouter as Router,
    Routes,
    Route,
    Link
} from "react-router-dom";
import Home from "./Home";
import Stuff from "./Stuff";
import Contact from "./Contact";

class Main extends Component {
    render() {
        return (
<Router>
    <div>
        <h1>Простий SPA-додаток</h1>
        <ul className="header">
            <li><Link to="/">Головна</Link></li>
            <li><Link to="/stuff">Продукти</Link></li>
            <li><Link to="/contact">Контакти</Link></li>
        </ul>
        <div className="content">
            <Routes>
                <Route exact path="/" element={<Home/>}/>
                <Route path="/stuff" element={<Stuff/>}/>
                <Route path="/contact" element={<Contact/>}/>
            </Routes>
        </div>
    </div>
</Router>


        );
    }
}

export default Main;

Як ви бачите, компонент Route містить властивість path. Значення, присвоєне цій властивості, визначає її стан активності. Коли path активний, відповідний компонент візуалізується. Наприклад, коли ми переходимо за посиланням Stuff (з path /stuff, заданою властивістю компонента NavLink ), шлях, значення path властивість якого дорівнює /stuff, також стає активним. Це означає, що контент компонента Stuff буде візуалізований.

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

 

Додавання правил CSS

На цьому етапі наш додаток ще не відформатований. У папці src створіть файл index.css та додайте до нього такі правила стилю:

body {
    background-color: #FFCC00;
    padding: 20px;
    margin: 0;
}
h1, h2, p, ul, li {
    font-family: sans-serif;
}
ul.header li {
    display: inline;
    list-style-type: none;
    margin: 0;
}
ul.header {
    background-color: #111;
    padding: 0;
}
ul.header li a {
    color: #FFF;
    font-weight: bold;
    text-decoration: none;
    padding: 20px;
    display: inline-block;
}
.content {
    background-color: #FFF;
    padding: 20px;
}
.content h2 {
    padding: 0;
    margin: 0;
}
.content li {
    margin-bottom: 10px;
}

Тепер нам потрібно послатись на цю таблицю стилів у коді програми:

import React from "react";
import ReactDOM from "react-dom";
import Main from "./Main";
import "./index.css";

ReactDOM.render(
        <Main/>,
    document.getElementById("root")
);

29 ВВЕДЕННЯ В REDUX

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

Візуалізація взаємозв'язку між функціональністю програми та її станом часто досить заплутана:

Вирішити цю загальну проблему підтримки стану програми покликаний контейнер станів Redux. Найпростіший спосіб зрозуміти, як працює Redux, це вивчити різні частини, з яких він складається. Перше, що нам потрібно, - це додаток:

Він може бути побудований в React, Angular, Vue, vanilla JS або в будь-якій іншій бібліотеці або фреймворку. Redux не дбає про те, як ваш додаток побудовано. Він дбає лише про те, щоб він працював зі станом додатку і його зберігав. У випадку Redux ми зберігаємо всі стани програми в одному місці, яке називається сховищем (Store).

Суть сховища полягає у тому, щоб зчитувати дані з нього було легко. Отримання інформації – зовсім інша історія. Ви додаєте новий стан (або змінюєте поточний) в сховище (store), використовуючи комбінацію дій (actions), що описують, що потрібно змінити, і редуктор (reducer), який визначає, який кінцевий стан буде в результаті цієї дії.

Виникає резонне питання: чому програма не може відразу оновити сховище?

Причина в масштабованості. Навіть для простих програм підтримка стану програми в синхронізації з діями програми – це важка робота. У складних програмах, в яких різні частини хочуть отримати доступ і змінити стан програми, про це не може бути мови. Цей кільцевий спосіб – рішення Redux для забезпечення простоти збереження стану програми і для простих, і для складних програм. Крім простоти, Redux допомагає підтримувати стан вашої програми передбачуваним. Ден Абрамов та Ендрю Кларк, творці Redux, інтерпретували передбачуваність так:

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

2. Ваш стан має бути доступним лише для читання і може бути змінений лише за допомогою дій. Як ви бачили на схемі вище, у Redux вам необхідно переконатися, що випадкові частини вашої програми не зможуть отримати доступ до сховища та змінити стан, збережений там. Єдиний спосіб, яким програма може змінити те, що знаходиться в сховищі, - це дії.

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

Ці три принципи можуть здатися трохи абстрактними, але коли ви почнете писати код Redux, то побачите, як вони втілюються в життя.

 

Створення простої програми за допомогою Redux

Освоюючи Redux, ми створимо простий консольний додаток без графічного інтерфейсу. Ця програма буде зберігати та відображати список улюблених кольорів. У програмі ви зможете додавати та видаляти кольори.

Спочатку нам потрібно створити новий HTML-документ та послатися на бібліотеку Redux. Це буде звичайний простий HTML-файл, який можна відкрити в браузері. Використовуючи редактор коду, створіть файл з іменем favoriteColors.html і додайте до нього наступний вміст:

<! DOCTYPE html>
<html>
<head>
    <title>Обрані кольори</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.js"></script>
</head>
<body>
<script>

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

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

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

Всередині елемента script додайте наступний код:

function addColor(value) {
    return {
        type: "ADD",
        color: value
    };
}
function removeColor(value) {
    return {
        type: "REMOVE",
        color: value
    };
}

У нас є дві функції: addColor та removeColor . Кожна приймає один аргумент і в результаті повертає об'єкт дії.

Для функції addColor об'єкт дії – це два наступні рядки:

type: "ADD",
color: value

Кожен об'єкт дії має властивість типу. Це ключове слово, яке сповіщає про те, що ви маєте намір робити. Крім того, будь-яка інша інформація, яку ви надсилаєте разом з діями, повністю залежить від вас. Оскільки ми зацікавлені у додаванні або видаленні значення кольору зі сховища, наш об'єкт дії також має властивість color, яка зберігає колір, що нас цікавить.

Повернемося до функцій addColor та removeColor. Обидві служать лише одній меті – поверненню дії. У світі Redux існує власна назва таких функцій. Вони відомі як творці дій (action creators), тому що вони створюють дії.

Управління редуктором

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

1. Забезпечує доступ до вихідного стану сховища;

2. Дозволяє перевірити дію, яка в даний час була запущена;

3. Дозволяє встановити новий стан сховища.

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

function favoriteColors(state, action) {
    if (state === undefined) {
        state = [];
    }
    if (action.type === "ADD") {
        return state.concat(action.color);
    } else if (action.type === "REMOVE") {
        return state.filter(function(item) {
            return item !== action.color;
        });
    } else {
        return state;
    }
}

Спочатку ми забезпечуємо те, що ми маємо певний стан:

if (state === undefined) {
    state = [];
}

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

Решта коду відповідає за наші дії. Зауважте, що редуктор отримує повний об'єкт дії через свій аргумент action. Це означає, що ви маєте доступ не тільки до властивості дії type, а й до всього, що ви зазначили раніше як частину визначення ваших дій.

У цьому прикладі, якщо тип нашої дії ADD, ми додаємо колір (вказаний властивістю color дії) масив стану. Якщо тип дії REMOVE, ми повертаємо новий масив із зазначеним кольором. Зрештою, якщо тип дії – те, чого ми не знаємо, ми повертаємо поточний стан, немодифікований:

if (action.type === "ADD") {
    return state.concat(action.color);
} else if (action.type === "REMOVE") {
    return state.filter(function(item) {
        return item !== action.color;
    });
} else {
    return state;
}

За більш детальною інформацією про роботу редуктора можна звернутися за наступним посиланням https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers

Речі, які ви ніколи не повинні робити всередині редуктора:

• Змінювати свої аргументи.

• Виконувати інші дії, такі як виклики API та переходи маршрутизації.

• Викликати нечисті функції, наприклад, Date.now() або Math.random().

Враховуючи самі аргументи, він повинен обчислити наступний стан і повернути його. Жодних сторонніх дій. Жодних викликів API. Ніяких змін. Тільки обчислення.

Ви можете побачити все це у коді нашого прикладу. Щоб додати нові значення кольору до масиву станів, ми використовували метод concat: він повертає зовсім новий масив, що складається зі старих значень і нового значення, яке ми додали. Функція push дасть нам той самий результат, але це порушує наше завдання, яке полягає у відсутності змін у існуючому стані. Щоб видалити значення кольору, ми продовжуємо дотримуватись нашої мети – не змінювати поточний стан. Ми використовуємо метод Filter, який повертає новий масив зі значенням, яке хочемо видалити.

 

Робота зі сховищем

Все, що залишилося зараз зробити, це зв'язати дії та редуктор зі сховищем. Спочатку ми маємо створити сховище. Одразу після функції favoriteColor додайте наступний код:

var store = Redux.createStore(favoriteColors);

Тут ми створюємо нове сховище, використовуючи метод createStore. Аргумент, який ми передаємо, - це favoriteColor редуктор, який ми створили раніше. Тепер у нас є повний цикл Redux для зберігання стану програми. У нас є сховище, редуктор та дії, які повідомляють редуктору, що робити.

Щоб подивитися, як все це працює, ми додамо (і видалимо) деякі кольори в (з) сховище. Для цього ми скористаємося методом dispatсh на об'єкті store, який приймає дію як аргумент.

store.dispatch(addColor("blue"));
store.dispatch(addColor("yellow"));
store.dispatch(addColor("green"));
store.dispatch(addColor("red"));
store.dispatch(addColor("gray"));
store.dispatch(addColor("orange"));
store.dispatch(removeColor("gray"));

Кожен виклик dispatсh посилає дію редуктору. Останній вживає заходів та виконує відповідну роботу для визначення нового стану. Щоб побачити поточний стан сховища, ви можете додати наступну інструкцію після всіх викликів dispatсh:

console.log(store.getState());

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

У реальних сценаріях ви хочете отримувати сповіщення щоразу, коли змінюється стан програми, наприклад, якщо ви хочете оновити інтерфейс або виконати інші завдання внаслідок деяких змін у сховищі. Для цього існує метод subscribe, який посилатиметься на функцію, яка буде викликатися щоразу, коли вміст сховища змінюється. Щоб побачити метод subscribe у дії, одразу після того, як ви визначили об'єкт сховища, внесіть такі зміни:

var store = Redux.createStore(favoriteColors);

store.subscribe(render);
function render() {
    console.log(store.getState());
}

Після того, як ви це зробите, знову запустіть програму. На цей раз, коли ви викликаєте dispatсh для запуску іншої дії, функція render викликається при зміні сховища.

30 СПІЛЬНЕ ВИКОРИСТАННЯ REDUX З REACT

Тепер, коли ви з'ясували, як працює Redux, давайте розглянемо чому Redux настільки популярний у проектах React. Розглянемо наступну ієрархію компонентів будь-якої довільної програми:

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

В ідеальному проекті дані, необхідні кожному компоненту, переносяться від предка до нащадка:

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

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

На цьому етапі нам потрібно визнати кілька проблем, які можуть виникнути внаслідок передачі даних по нашим компонентам:

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

2. Щоразу, коли стан змінюється чи передається властивість, всі задіяні компоненти викликаються на повторний рендерінг. Щоб інтерфейс користувача був синхронізований з поточним станом, ця поведінка досить непогана. Як вже згадувалося, багато компонентів не вимагають повторного рендерингу, коли вони просто передають значення від предка до нащадка, без додаткового введення. Ми розглянули способи мінімізації цього повторного рендерингу, налаштуванням mustComponentUpdate або покладаючись на компонент PureComponent, але обидва підходи складні в синхронізації даних, тому що потреби програми повинні розвиватися.

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

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

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

Ви можете ініціювати зміну стану та залучити лише ті компоненти, на які безпосередньо впливають. Це зменшує кількість витрат, які вам інакше довелося б підтримувати, щоб дані (і будь-які зміни) потрапляли до місця призначення, не викликаючи непотрібних методів render.

Тепер перейдемо на один рівень вище. З архітектурного погляду Redux, який ви отримали в минулому розділі, показаний нижче:

Крім сховища, нам все одно доведеться працювати з діями, редуктором та всіма іншими пов'язаними з ними об'єктами, які складають Redux. Єдина відмінність полягає в тому, що наша програма побудована з використанням React, і на цій різниці (і на тому, як відбувається взаємодія з Redux) ми і сфокусуємо нашу увагу.

 

Керування станами React за допомогою Redux

Для підключення Redux до React-додатку необхідно виконати два кроки:

Щоб розібратися з реалізацією цих двох кроків, ми створимо простий додаток-лічильник. Наш додаток буде мати кнопки «плюс» та «мінус», щоб збільшувати та зменшувати значення лічильника.

Як перетинаються React та Redux

Спочатку розглянемо структуру програми. Без урахування даних та управління станом у нас буде лише два компоненти:

У нас будуть компоненти App та Counter. Реалізуючи його з використанням звичайного стану, ми створили б об'єкт стану в компоненті Counter і ввели змінну, значення якої збільшується або зменшується в залежності від того, яку кнопку ми натискаємо. Додавши до додатку Redux, схема компонентів виглядатиме наступним чином:

Підключення Redux до додатку включає два кроки. Перший крок для забезпечення доступу до сховища Redux виконується компонентом Provider. Другий крок, надання компонентом Connect будь-якому зацікавленому компоненту доступу до наших дій.

Компонент Provider — шлюз для отримання функціональності у нашому додатку React. Він несе відповідальність за збереження посилання на сховище та забезпечення того, щоб усі компоненти в програмі мали доступ до нього. Він може зробити це, будучи верхнім компонентом в ієрархії компонентів. Ця позиція дозволяє легко передавати всі дані, пов'язані з Redux, по всьому додатку.

Компонент Connect, відомий як Higher Order Component (компонент вищого порядку – reactjs.org/docs/higherorder-components.html), або HOC. Такі компоненти забезпечують послідовний спосіб розширити функціональність існуючого компонента, розширюючи його і вводячи додаткові функціональні можливості. Схема показує нам: кінцевий результат полягає в тому, що завдяки компоненту Connect компонент Counter має доступ до будь-яких дій та передачі, необхідних для роботи зі сховищем Redux без необхідності писати будь-який спеціальний код для його доступу.

Компоненти вищого порядку, як Provider, так і Connect, створюють відносини, які дозволяють будь-якому додатку React легко працювати з унікальним режимом управління станом програми Redux.

 

Приклад створення додатку з використанням Redux

Для початку створимо програму за допомогою команди create-react-app, яку назвемо reduxcounter:

create-react-app reduxcounter

Тепер встановимо залежності Redux та React Redux. В оболонці командного рядка перейдіть до папки reduxcounter та виконайте наступну команду

npm install redux

Після того як бібліотека Redux повністю встановлена, необхідно передати весь вміст React Redux

npm install react-redux

В каталозі public створіть файл index.html і додайте до нього наступний HTML код:

<!DOCTYPE html>
<html>
<head>
    <title>Лічильник Redux</title>
</head>
<body>
<div id="container">
</div>
</body>
</html>

Потім створимо JavaScript-сценарій, який стане точкою входу до нашої програми. У папці src створіть файл index.js та додайте до нього наступний вміст:

import React, {Component} from "react";
import ReactDOM from "react-dom";
import {createStore} from "redux";
import {Provider} from "react-redux";
import counter from "./reducer";
import App from "./App";
import "./index.css";

var destination = document.querySelector("#container");

// Сховище (Store)
var store = createStore(counter);
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    destination
);

Спочатку ми ініціалізуємо сховище Redux, використовуючи метод createStore, який приймає редуктор як аргумент. Редуктор посилається на змінну counter, що знаходиться у файлі з ім'ям reducer.js.

Після створення сховища ми передаємо його компоненту Provider як властивість компонента. Компонент Provider призначений для використання як зовнішній компонент у додатку, щоб гарантувати, що кожен компонент має доступ до сховища Redux та пов'язаної з ним функціональності:

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    destination
);

Далі створимо редуктор – файл з ім'ям reducer.js у кореневому каталозі. Створивши його, додайте до нього наступний JavaScript-код

// Редуктор
function counter(state, action) {
    if (state === undefined) {
        return {count: 0};
    }
    var count = state.count;
    switch (action.type) {
        case "increase":
            return {count: count + 1};
        case "decrease":
            return {count: count - 1};
default:
    return state;
}
}
export default counter;

У нас є змінна count, якій ми присвоюємо значення 0, якщо стан порожній. Цей редуктор опрацьовуватиме два типи дій: increase та decrease. Якщо використовується дія increase, ми збільшуємо значення змінної count на 1. Якщо використовується дія decrease, то зменшуємо значення змінної count на 1.

Тепер опустимося на один рівень вниз і реалізуємо компоненту App. У папці src створіть файл під назвою App.js. У цей файл додайте наступне:

import {connect} from "react-redux";
import Counter from "./Counter";

// Зіставлення стану Redux з властивостями компонента
function mapStateToProps(state) {
    return {
        countValue: state.count;
    };
}
// Дія
var increaseAction = {type: "increase"};
var decreaseAction = {type: "decrease"};

// Зіставлення дій Redux з властивостями компонента
function mapDispatchToProps(dispatch) {
    return {
        increaseCount: function() {
            return dispatch(increaseAction);
        },
        decreaseCount: function() {
            return dispatch(decreaseAction);
        }
    };
}
// HOC-компонент

var connectedComponent = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

export default connectedComponent;

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

Насамперед функція mapStateToProps:

// Зіставлення стану Redux з властивостями компонента
function mapStateToProps(state) {
    return {
        countValue: state.count;
    };
}

Ця функція підписується на всі оновлення сховища (Store) і викликається, коли щоcь змінюється. Вона повертає об'єкт, що містить дані сховища, які ви хочете передати як властивості компоненту. У нашому випадку це об'єкт, який містить властивість countValue, значення якого представлено властивістю count із сховища.

Надання значення сховища як властивості — це перша частина того, що нам потрібно зробити. Потім потрібно надати нашому компоненту доступ до дій як властивостей. Наступний код відповідає за це

// Дія
var increaseAction = {type: "increase"};
var decreaseAction = {type: "decrease"};

// Зіставлення дій Redux з властивостями компонента
function mapDispatchToProps(dispatch) {
    return {
        increaseCount: function() {
            return dispatch(increaseAction);
        },
        decreaseCount: function() {
            return dispatch(decreaseAction);
        }
    };
}

За допомогою функції mapDispatchToProps ии повертаємо об'єкт, що містить іменадвох функцій, які компонент може викликати для надсилання змін до сховища. Функція increaseCount запускає відправку з типом дії increase. Функція decreaseCount запускає відправку з типом дії decrease. Якщо подивитися на редуктор, який було створено раніше, то можна побачити, що обидві ці функції змінюють значення count, яке ми зберігаємо в сховищі.

Тепер залишилося зробити так, щоб будь-який компонент міг отримати доступ до цих властивостей. Для цього використовується функція connect.

var connectedComponent = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

Ця функція створює HOC-компонент Connect. Він приймає команди mapStateToProps та mapDispatchToProps як аргументи, і передає все це у вказаний компонент Counter. Кінцевий результат всього цього коду еквівалентний візуалізації наступного:

<Connect>
    <Counter increaseCount={increaseCount}
             decreaseCount={decreaseCount}
             countValue={countValue}/>
</Connect>

Компонент Counter отримує доступ до функцій increaseCount, decreaseCount та countValue. Зверніть увагу, що відсутні функції рендерингу. Все це автоматично обробляється React та його HOC-компонентом.

Залишилося створити компонент Counter. У каталозі src створіть файл Counter.js. Додайте до нього наступний вміст:

import React, {Component} from "react";
class Counter extends Component {
    render() {
        return (
            <div className="container">
                <button className="buttons"
                        onClick={this.props.decreaseCount}>-</button>
                <span>{this.props.countValue}</span>
                <button className="buttons"
                        onClick={this.props.increaseCount}>+</button>
            </div>
        );
    }
}
export default Counter;

Останнє, що залишилося – це додати стиль.

У папці src створіть файл index.css.

body {
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    display: flex;
    justify-content: center;
    background-color: #8E7C93;
}

.container {
    background-color: #FFF;
    margin: 100px;
    padding: 10px;
    border-radius: 3px;
    width: 200px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.buttons {
    background-color: transparent;
    border: none;
    font-size: 16px;
    font-weight: bold;
    border-radius: 3px;
    transition: all .15s ease-in;
}

.buttons: hover: nth-child(1)
{
    background-color: #F45B69;
}
.buttons: hover: nth-child(3)
{
    background-color: #C0DFA1;
}

Якщо запустити програму в браузері (виконавши команду npm start), ви побачите, що лічильник працює так, як очікувалося


© 2006—2023 СумДУ