22 марта

Введение в React

JavaScriptПрограммированиеПрофессиональная литератураReactJS
Tutorial
Оглавление
1. Getting started with React
....1.1 Методы добавления React
....1.2 Выбор метода добавления
........1.2.1 Добавление React с помощью тэга <script />
........1.2.2 Добавление React с помощью create-react-app
2. Basically React
....2.1 React object
....2.2 React element
........2.2.1 CreateElement
........2.2.2 CloneElement
........2.2.3 IsValidElement
........2.2.4 Children
....2.3 React компоненты
........2.3.1 React.Component
........2.3.2 React.Fragment
.........2.3.3 State
.........2.3.4 Events
.........2.3.5 Lifecycle
.........2.3.6 Refs
....2.4 ReactDOM
........2.4.1 Render
........2.4.2 Hydrate
........2.4.3 UnmountComponentAtNode
........2.4.4 CreatePortal
3. Other topics
....3.1 Lists and Keys
....3.2 Error Handling
........3.2.1 getDerivedStateFromError
........3.2.2 componentDidCatch
Заключение

React — JavaScript-библиотека с открытым исходным кодом для разработки пользовательских интерфейсов.

1. Getting started with React


1.1 Методы добавления React


Существует 2 основных метода для добавления React на сайт:

  1. С помощью тэга <script />
  2. С помощью create-react-app

1.2 Выбор метода добавления


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

1.2.1 Добавление React с помощью тэга <script />


Шаг 1 Добавьте 3 тега в контейнер head на вышей странице:
<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/babel-standalone@6.15.0/babel.min.js"></script>

Здесь подключаются библиотеки React и React-dom, а также компилятор babel.
Babel не является обязательным для использования React, но полезным для написания кода UI, с помощью JSX.

Шаг 2 Добавьте пустой контейнер на вашу страницу чтобы отметить место, где вы хотите что-либо отобразить с помощью React.
Шаг 3 Теперь вы можете использовать React вместе с JSX в любом теге script, добавив к нему атрибут type=«text/babel».

Пример:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>My first React app</title>
	<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  	<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  	<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
	<div id="root"></div>

	<script type="text/babel">
  		ReactDOM.render(<h1>Hello world</h1>, document.querySelector("#root"));
  	</script>
</body>
</html>

1.2.2 Добавление React с помощью create-react-app


Инструменты, используемые для разработки React, опираются на Node.js, поэтому первое что вам необходимо сделать, это установить Node, что бы использовать npm.

Пакет create-react-app является стандартным способом создания и управления сложными пакетами React и предоставляет разработчикам полный набор инструментов. Используйте create-react-app, для создания нового React приложения.

После установки nodejs, для установки create-react-app:

// npx
npx create-react-app my-app

// npm
npm init react-app my-app

// yarn
yarn create react-app my-app

Выбор за вами какой инструмент использовать (npx, npm, yarn) для создания React приложения.

Когда вы создаете новое приложение, CLI будет использовать Yarn для установки зависимостей (если они доступны). Если у вас установлен Yarn, но вы предпочитаете использовать npm, вы можете добавить --use-npm к команде создания. Например:

npx create-react-app my-app --use-npm

Выполнение любой из этих команд создаст каталог с именем my-app внутри текущей папки. Внутри этого каталога будет сгенерирована исходная структура проекта и установлены необходимые зависимости:

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js

Для запуска приложения в режиме разработки перейдите в папку с вашим приложением и выполните npm start или yarn start:

cd my-app
npm start

Страница автоматически перезагрузится, если вы внесете изменения в код. Вы увидите ошибки сборки и предупреждения в консоли. Откройте localhost:3000, чтобы просмотреть приложение в браузере.

Обратите внимание! Для сборки проекта эти файлы должны существовать с точными именами файлов:

  • public / index.html — шаблон страницы;
  • src / index.js — это точка входа JavaScript.

Вы можете удалять или переименовывать другие файлы.

2. Basically React


2.1 React object


React — это точка входа в библиотеку React. При подключении React с помощью тега script, API верхнего уровня доступны в глобальном объекте React, а если вы используете ES6 и create-react-app, вы должны импортировать React объект:

import React from 'react';

Если же вы используете ES5 и create-react-app, вы можете импортировать React объект следующем образом:

var React = require('react');

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

2.2 React element


React элемент — минимальная единица React-приложения, объект описывающий то, что вы хотите увидеть на странице. Элементы React не изменяемые (immutable). То есть состояние элементов React не может быть изменено после создания.

Для создания React элементов вам нужно использовать методы createElement() или
createFactory(). Последний считается устаревшим и его использование не рекомендуется поэтому не будем останавливаться на нём. Если вы используете JSX, то вам не придётся вызывать данные методы.

React предоставляет методы для клонирования — cloneElement, проверки — isValidElement() и работой с структурой данных props.children это React.Children (семейство методов React.Children).

Для отображения React элементов необходимо вызвать метод Render объекта ReactDom. После отображения (рендеринга) React элемента вы не можете изменить его потомков или атрибуты. Единственным решением является повторный вызов ReactDom.render(). На практике не советуется повторный вызов этого метода. Поэтому для интерактивных элементов вам необходимо использовать React компоненты.

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

В React элементах допустимо использование встраиваемых выражений Javascript.

Пример использования встраиваемого выражения:

const name = 'Michael';
var nameUser = () => "Franklin";
const element = <h1>Здравствуй, {name}</h1>;
const element_2 = <h1>Здравствуй, {nameUser()}</h1>;

ReactDOM.render(element, document.getElementById("root"));
ReactDOM.render(element_2, document.getElementById("root_2"));

//Output
Здравствуй, Michael
Здравствуй, Franklin

Если вы не используете JSX, то вы можете использовать встраиваемые выражения передав их как один из аргументов методу createElement.

Пример использования встраиваемого выражения с методом createElement:

var element = React.createElement("h1", null, "Здравствуй, ", name);
var element_2 = React.createElement("h1", null, "Здравствуй, ", nameUser());

2.2.1 React.createElement


Для создания React элемента используйте метод createElement:

React.createElement(
  type,
  [props],
  [...children]
);

  • type — JavaSacript строка, содержащая имя тега, React-компонент или React-фрагмент;
  • props — props (свойство экземпляра React объекта)
  • children — Дочерний элемент для type.

Создание элемента без использования JSX, с использованием React.createElement:

element = React.createElement(Message, {
  message: "Hello, world"
}, React.createElement("p", null, "Some child element"));

Если же вы используете JSX, то у вас нет необходимости в специальных методах для создания React элемента.

Создание React элемента с использованием JSX:

element = <Message message="Hello, world">
  <p>Some child element</p>
</Message>;

Как вы могли заметить оба примера создают один и тот же объект. Таким образом из примера выше можно сделать вывод о том, что каждый элемент JSX это просто синтаксический сахар для вызова React.createElement().

Вы можете проверить это сами. Используя для этого онлайн компилятор Babel.

2.2.2 React.cloneElement


React.cloneElement — метод позволяющий клонировать React элементы.

React.cloneElement(
  element,
  [props],
  [...children]
)

Возвращает новый React элемент, используя переданный React элемент в качестве отправной точки. Аргументы такие же как у React.createElement() за исключением того, что первый параметр не имя нового тэга, а имеющиеся React элемент. Полученный элемент будет иметь пропсы исходного элемента, а новые пропсы будут поверхностно слиты воедино. Новые дочерние элементы заменят существующие. key и ref из исходного элемента будут сохранены.

Пример:

var a = <p>Some React element</p>;
var b = React.cloneElement(a);

ReactDOM.render(b, document.getElementById("root"));
//Output 
Some React element

2.2.3 React.isValidElement


React.isValidElement(object) — Проверяет, что объект является элементом React. Возвращает true в случае если объект является элементом React или false в случае если он не является элементом React.

const A = <p>Some text</p>;
const B = {};

if (React.isValidElement(A))
  console.info("A is React Element");

if (React.isValidElement(B))
  console.info("B is React Element");
else
  console.error("B is not a React Element");

// Output
A is React Element
B is not a React Element

2.2.4 React.Children


React.Children предоставляет функции для работы с непрозрачной структурой данных this.props.children. Если проще, то props.children это то, что вы включаете между открывающим и закрывающим тегами при вызове компонента.

Простой пример:

function Chill(props) {
        return <h1>Hello {props.children}</h1>
}

ReactDom.render(<Chill>Mike</Chill>, element); // Hello Mike

Таким образом props.children может помочь сделать ваши компоненты React более гибкими и многократно используемыми. Вы можете использовать props.children для компонентов, которые представляют «общие блоки» и которые «не знают своих детей раньше времени». То есть в примере выше компонент Chill это шаблон, который можно использовать многократно с разным результатом. Конечно такой упрощенный пример сильно напоминает использование пропсов, но с помощью props.children вы можете строить более сложные структуры данных.

Давайте в вкратце рассмотрим методы для работы с props.children.

  • React.Children.map — создаёт новый массив с результатом вызова указанной функции для каждого элемента массива;
  • React.Children.forEach — выполняет указанную функцию один раз для каждого элемента в массиве;
  • React.Children.count — возвращает общее количество компонентов в children;
  • React.Children.only — если у children есть только один потомок (React элемент), то возвращает его иначе выдаёт ошибку;
  • React.Children.toArray — возвращает массива из children с ключами, заданные каждому дочернему элементу

React.Children.map

React.Children.map(children, function[(thisArg)])

Похож на Array.prototype.map(). Создаёт новый массив с результатом вызова указанной функции для каждого элемента массива. Вызывает функцию для каждого непосредственного потомка, содержащегося в children передавая их по очереди в thisArg. Если children — это массив, он будет пройден, и функция будет вызвана для каждого потомка в массиве. Если children равен null или undefined, этот метод вернёт null или undefined, а не массив. Если children — это React.Fragment, он будет рассматриваться как целый потомок, а элементы внутри не будут пройдены. Метод map вызывает переданную функцию callback один раз для каждого элемента, в порядке их появления и конструирует новый массив из результатов её вызова.

React.Children.forEach

React.Children.forEach(children, function[(thisArg)])

Похож на Array.prototype.forEach(). Выполняет указанную функцию один раз для каждого элемента в массиве. В отличие от React.Children.map не возвращает массив из результатов вызова функции. Возвращает ubdefined.

Пример:

var users = ["Mike", "Bill", "Scott"];

function Chill(props) {
    return <span>{React.Children.map(props.children, (thisArg) => <p>Hello {thisArg}</p>)}</span>;
}

render (<Chill>{users}</Chill>, element); // Hello Mike, Hello Bill, Hello Scott

Вы множите найти больше примеров тут и тут.

2.3 React компоненты


React компонент — функция возвращающая React-элемент или JavaScript класс реализующий метод render() который возвращает React-элемент. Соответственно компоненты, объявленные с помощью JavaScript функции принято называть «функциональными компонентами» а компоненты, объявленные с помощью JavaScript классов принято называть «классовыми компонентами».

Компоненты, объявленные как функции JavaScript не могут содержать «состояния», поэтому их также принято называть stateless components, то есть, компоненты без состояния, а компонентные объявленные как JavaScript класс могу содержать «состояние», поэтому их называют statefull components, то есть, компоненты с состоянием. *На самом деле вы можете добиться похожей функциональности и от функциональных компонентов используя хуки.

Классовые React-компоненты могут быть объявлены путём создания подклассов React.Component или React.PureComponent. Последний отличается лишь тем, что он реализует метод shouldComponentUpdate() (см. жизненный цикл компонентов).

Функциональный React-компонент это функция JavaScript, которая может быть обернута в React.memo. React.memo похож на React.PureComponent только предназначен для функциональных компонентов.

Пропсы — это просто аргументы передоверяемые компоненту. Когда React встречает JSX-атрибуты он собирает их в один объект и передаёт компоненту. Этот объект и называется «пропсы» (props).

Пример использования пропсов в функциональном компоненте:

function SayHi(props) {
    return <h1>Привет, {props.name}</h1>;
}

class SayHi_2 extends React.Component {
    render() {
        return <h1>Привет, {this.props.name}</h1>;
    }
}

ReactDOM.render(<SayHi name="Mark" />, document.getElementById("root"));
ReactDOM.render(<SayHi_2 name="Bob" />, document.getElementById("root_2"));

//Output
Привет, Mark
привет, Bob

Запомните! Названия типов пользовательских компонентов должны начинаться с большой буквы!!!
React-компоненты обязаны вести себя как чистые функции по отношению к своим пропсам.

Компонент никогда не должен что-то записывать в свои пропсы — вне зависимости от того, функциональный он или классовый.

2.3.1 React.Component


React.Component — это базовый класс для компонентов React. Для создания React-компонента наследуйте свойства и методы от React.Component:

class myComponent extends React.Component {
  render() {
    return <h1>Hello, world</h1>;
  }
}

Каждый компонент имеет методы жизненного цикла, которые закономерно вызываются при монтировании, обновление и размонтировании компонента. Переопределение такого метода позволяет выполнять код на конкретном этапе этого процесса. (см. 2.3.5 Lifecycle).

2.3.2 React.Fragment


Как вы могли заметить любой React элемент и компонент должен возвращать только один узел (один html тэг). Если же вам необходимо отрендерить несколько тегов, то они в обязательном порядке должны быть обернуты в один контейнер иначе вы получите ошибку. Но что же делать, если не хочется создавать лишние контейнеры? Ответ: React.Fragment.

Компонент React.Fragment позволяет возвращать несколько элементов в методе render() без создания дополнительного элемента DOM:

render() {
    return (
       <React.Fragment>
            <p>Some text</p>
            <h2>Tittle</h2>
      </React.Fragment>
    );
}
Конечно, если вы не используете Jsx, то ваш код будет выглядеть так:

React.createElement(React.Fragment, null, React.createElement("p", null, "Some text"), React.createElement("h2", null, "Tittle"));

В React v16.2.0 завезли новый синтаксический сахар для React.Fragment, с этой версии вы можете просто писать пустой html тэг, который будет оборачиваться в React.Fragment:

render() {
    return (
       <>
            <p>Some text</p>
            <h2>Tittle</h2>
      </>
    );
}

2.3.3 State


Состояние компонента (State) — это приватное свойство (state) классовых React компонентов. Обновляя состояние с помощью метода setState() компонент рендерится повторно. Из этого следует запомнить что компонент не будет рендерится заново если изменять состояние напрямую (без вызова setState()).

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

class Counter extends React.Component {
    constructor(props) {
        super(props);

        this.state = { count: 0 };
        this.incrementCount = this.incrementCount.bind(this);
    }
    incrementCount() {
        this.setState(state => ({ count: state.count + 1 }));
    }

    render() {
        return <div>
            <p>Count = {this.state.count}</p>
            <button onClick={this.incrementCount}>Add</button>
        </div>
    }
}
ReactDOM.render(<Counter />, document.getElementById("root"));

Давайте разберём код по частям.

Ключевое слово super() используется как функция, вызывающая родительский конструктор. Её необходимо вызвать до первого обращения к ключевому слову this в теле конструктора. Поэтому прежде чем инициализировать начальное состояние компонента необходимо вызвать super(). Классовые компоненты всегда должны вызывать базовый конструктор с аргументом props. Даже если вы не используете пропсы.

Для методов необходимо вызывать метод bind() который наследуется из Function.prototype.bind() он создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. Это нужно потому что методы класса в JavaScript по умолчанию не привязаны к контексту. Поэтому this будет undefined в момент вызова функции если не привязать ее к контексту. Есть способ позволяющий избежать вызова bind(), это использование синтаксиса общедоступных полей классов:

incrementCount = () => {
        this.setState(state => ({ count: state.count + 1 }));
}

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

setState() использует текущее значение состояния для его обновления. Поэтому вместо объекта с новым состоянием передаётся функция получающая текущее состояние как аргумент. Дело в том что вызовы setState являются асинхронными из за чего this.state в некоторых случаях может отображать неправильное значение. Для понимания этого давайте взглянем на синтаксис метода setState:

setState(updater[, callback])

setState() напоминает функцию fetch() делающая запрос. Метод setState() не всегда обновляет компонент сразу. Он может группировать или откладывать обновление до следующего раза. Это делает чтение this.state сразу после вызова setState() потенциальной ловушкой. Первый аргумент функции updater имеет следующий вид:

(state[, props]) => stateChange

здесь state — ссылка на состояние компонента при изменении. Как state, так и props, полученные функцией, гарантированно будут обновлены. Второй параметр в setState() — дополнительный колбэк, который выполняется после того, как исполнится setState и произойдёт повторный рендер компонента. Если же следующее состояние не зависит от текущего тогда в качестве первого аргумента вы можете просто передать объект с новым состоянием.

Обновления состояния объединяются!

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

constructor(props) {
    super(props);
    
this.state = { value_1: true, value_2: false };
  }

Оба поля можно обновить по отдельности с помощью отдельных вызовов setState()

changeValue_1() {
    this.setState(state => ({ value_1: !state.value_1 }));
}
changeValue_2() {
    this.setState(state => ({ value_2: !state.value_2 }));
}

Состояния объединяются поверхностно, поэтому вызов changeValue_1() оставляет value_2 нетронутым, но полностью заменяет value_1.

Состояние доступно только для самого компонента и скрыто от других. То есть this.state является приватным свойством.

Компонент может передать своё состояние вниз по дереву в виде пропсов дочерних компонентов.

2.3.4 Events


Обработка событии в React похожа на таковую в DOM-элементах за исключением синтаксических особенностей и некоторых особенностей реализации.

// In HTML
<button onclick="handler()">Button</button>

// In Jsx
<button onClick={handler}>Button</button>

В React нельзя предотвратить события по умолчанию вернув false из обработчика события. Для этого нужно вызвать preventDefault().
function ActionLink() {
  function handleClick(e) {
    e.preventDefault(); // отмена события по умолчанию
  }
  return <a href="#" onClick={handleClick}>Link</a>;
}

Обработчики событий также получают объект Event, который в React называют SyntheticEvent. После вызова обработчика события объект SyntheticEvent повторно используется, а все его свойства будут очищены, поэтому нельзя использовать синтетические события асинхронно.
function onClick(event) {
  console.log(event); // => null-объект.
  console.log(event.type); // => "click"
  const eventType = event.type; // => "click"

  setTimeout(function() {
    console.log(event.type); // => null
    console.log(eventType); // => "click"
  }, 0);

  // Не сработает, поскольку this.state.clickEvent будет содержать только null-значения.
  this.setState({clickEvent: event});

  // По-прежнему можно экспортировать свойства события.
  this.setState({eventType: event.type});
}

Но если же вы всё таки хотите использовать события асинхронно то вызывайте event.persist() на событии. Тогда оно будет извлечено из пула, что позволит вашему коду удерживать ссылки на это событие.

Список всех поддерживаемых событий вы можете посмотреть тут.

2.3.5 Lifecycle


Каждый компонент имеет несколько «методов жизненного цикла». Переопределение такого метода позволяет выполнять код на конкретном этапе этого процесса. Давайте рассмотрим все три этапа и разберёмся в какой последовательности, какие методы вызываются на различных этапах. И собственно, как вы можете использовать эти методы.

Диаграмма жизненного цикла:

image

Монтирование

При монтирований (создание экземпляра компонента и его вставке в DOM) закономерно вызываются следующие методы в указанном порядке:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

constructor

constructor(props)

Конструктор — самый первый метод жизненного цикла, вызывается до того, как компонент будет примонтирован.

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

Как правильно определять состояние и привязывать методы к контексту в конструкторе я уже детально объяснил. (см. 2.3.3 State).

getDerivedStateFromProps()
static getDerivedStateFromProps(props, state)

getDerivedStateFromProps вызывается непосредственно перед вызовом метода render, как при начальном монтировании, так и при последующих обновлениях. Он должен вернуть объект для обновления состояния или null, чтобы ничего не обновлять. Этот метод существует для редких случаев, когда состояние зависит от изменений в пропсах. getDerivedStateFromProps существует только для одной цели. Он позволяет компоненту обновлять свое внутреннее состояние в результате изменений в props. Для лучшего понимания прочитайте статью Brian Vaughn, You Probably Don't Need Derived State.

Так как это статический метод, он не имеет доступа к экземпляру компонента. Чтобы использовать пропсы, состояние и методы класса в getDerivedStateFromProps(), их нужно вынести за пределы класса в виде чистых функций.

Если вы хотите повторно использовать код между getDerivedStateFromProps() и другими методами класса, извлеките чистые функции пропсов и состояния компонента и поместите их вне определения класса.

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

render()

render()

При вызове он проверяет this.props и this.state и возвращает один из следующих вариантов:

  • Элемент React
  • Массив
  • Портал
  • Строку
  • Число
  • Booleans или null

Функция render() должна быть чистой.

Взаимодействовать с браузером необходимо в componentDidMount() или других методах жизненного цикла. Чистый render() делает компонент понятным.

В отличие от других методов жизненного цикла, он является единственным обязательным методом в классовом компоненте.

componentDidMount()

componentDidMount()

componentDidMount() вызывается сразу после монтирования (то есть, вставки компонента в DOM). В этом методе должны происходить действия, которые требуют наличия DOM-узлов. Например, работа с анимацией или создание сетевых запросов.

Вы можете сразу вызвать setState() в componentDidMount(). Это вызовет дополнительный рендер перед тем, как браузер обновит экран. Гарантируется, что пользователь не увидит промежуточное состояние, даже если render() будет вызываться дважды. Правда при таком подходе возможны проблемы с производительностью. Начальное состояние лучше объявить в constructor(). Однако, это может быть необходимо для случаев, когда нужно измерить размер или положение DOM-узла, на основе которого происходит рендер. Например, для модальных окон или всплывающих подсказок.

Обновление

Если монтирование происходит при создании экземпляра компонента и его рендеринге (передаче компонента в метод ReactDom.render()), то обновление происходит при последующих вызовах ReactDom.render(). Обновление происходит при изменении пропсов или состояния компонента. При повторном рендере компонента закономерно вызываются следующие методы в указанном порядке:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

Правда здесь стоит отметить, что метод shouldComponentUpdate() не будет вызван если к обновлению привёл вызов forceUpdate(). Можно заключить что не только обновление состояния и пропсов приводит к обновлению компонента, но также и метод forceUpdate().

forceUpdate()

component.forceUpdate(callback)

Если ваш метод render() зависит от некоторых других данных, вы можете указать React необходимость в повторном рендере, вызвав forceUpdate().

Вызов forceUpdate() приведёт к выполнению метода render() в компоненте, пропуская shouldComponentUpdate(). Это вызовет обычные методы жизненного цикла для дочерних компонентов, включая shouldComponentUpdate() каждого дочернего компонента. React по-прежнему будет обновлять DOM только в случае изменения разметки.

В этом примере при нажатие на кнопку будет вызван handleClick который приведёт к обновлению компонента и вызову getSnapshotBeforeUpdate в Tittle который изменит значение свойства button.click перед тем как компонент будет отображён. В итоге при каждом нажатии кнопки надпись в Title будет сменятся.

class Clicker extends React.Component {
    constructor(props) {
        super(props);
    }
    handleClick = () => {
        this.forceUpdate();
    }
    render() {
        return <React.Fragment>
            <Tittle></Tittle>
            <button onClick={this.handleClick}>Click me</button>
        </React.Fragment>;
    }
}
class Tittle extends React.Component {
    constructor(props) {
        super(props);

        this.button = { click: false };
    }
    getSnapshotBeforeUpdate() {
        this.button.click = (this.button.click) ? false : true;
    }
    render() {
        if(this.button.click)
            return <h1>Button enabled</h1>;
        else
            return <h1>Button disabled</h1>;
    }
}
ReactDom.render(<Clicker />, document.getElementById("root"));

Конечно это можно было реализовать проще, просто изменяя состояние Clicker в handleClick и передовая его как props в Tittle. Но этот пример хорошо демонстрирует работу forceUpdate(). Можно сказать, что forceUpdate() это замена для setState() когда необходимо принудительно обновить компонент. Вы можете использовать этот метод в двух случаях: когда вы не хотите реализовывать состояние компонента но хотите иметь способ принудительного обновления либо если вы хотите пропустить shouldComponentUpdate() при обновление компонента.

getDerivedStateFromProps()

Метод getDerivedStateFromProps() уже был рассмотрен выше так как он вызывается как при начальном монтировании, так и при обновлении компонента. Не ясно для чего он вызывается при монтировании. Поскольку его основной задачей по задумке разработчиков это возвращение объекта для обновления состояния или null, чтобы ничего не обновлять.

shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate указывает на необходимость следующего рендера на основе изменений состояния и пропсов. По умолчанию происходит повторный рендер при любом изменении состояния. В большинстве случаев вы должны полагаться на это поведение. Но бывают специфические случае, когда, к примеру, необходимость в обновление компонента возникает только при изменение конкретного props или state, а во всех остальных случаях не требуется. Вы можете сравнить this.props с nextProps, а this.state с nextState, верните false чтобы пропустить обновление React.

Обратите внимание возврат false не предотвращает повторный рендер дочерних компонентов при изменении их состояния.

Значение по умолчанию равно true. Этот метод не вызывается при первом рендере или когда используется forceUpdate().

Поскольку лишние вызовы ReactDom.render() могут плохо сказаться на скорости работы вашего приложения, этот метод нужен только для повышения производительности. Не опирайтесь на возможность shouldComponentUpdate() «предотвратить» рендер, это может привести к багам.

Как говорилось ранее (см. 2.3 React компоненты) классовые React-компоненты которые объявляются путём создания подкласса React.PureComponent, а не React.Component по умолчанию реализуют метод shouldComponentUpdate().

Метод shouldComponentUpdate() базового класса React.PureComponent делает только поверхностное сравнение объектов. Если они содержат сложные структуры данных, это может привести к неправильной работе для более глубоких различий (то есть, различий, не выраженных на поверхности структуры). Наследуйте класс PureComponent только тогда, когда вы ожидаете использовать простые пропсы и состояние, или используйте forceUpdate(), когда знаете, что вложенные структуры данных изменились.

При обновление сразу после shouldComponentUpdate() вызывается render() принцип работы которого рассматривался выше.

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() вызывается прямо перед этапом «фиксирования» (например, перед добавлением в DOM). Он позволяет вашему компоненту брать некоторую информацию из DOM (например, положение прокрутки) перед её возможным изменением. Любое значение, возвращаемое этим методом жизненного цикла, будет передано как параметр componentDidUpdate().

Хотя этот метод и вызывается после render() в нем вы все еще можете получить «снимок», то как выглядит DOM до рендера.

Значение снимка (или null) должно быть возвращено из этого метода.

componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() вызывается сразу после обновления DOM. Он подходит для выполнения, таких действии которые требуют наличие DOM. Также он подходит для выполнения сетевых запросов, которые выполняются на основании результата сравнения текущих пропсов с предыдущими. Если пропсы не изменились, новый запрос может и не требоваться.

В componentDidUpdate() можно вызывать setState(), однако его необходимо обернуть в условие, чтобы не возникла бесконечная рекурсия.

В тех редких случаях когда реализован метод жизненного цикла getSnapshotBeforeUpdate(), его результат передаётся componentDidUpdate() в качестве третьего параметра snapshot.

componentDidUpdate() не вызывается, если shouldComponentUpdate() возвращает false.

Размонтирование

При удалении компонента из DOM вызывается метод componentWillUnmount().

componentWillUnmount()

componentWillUnmount()

componentWillUnmount() вызывается непосредственно перед размонтированием (например при вызове ReactDOM.unmountComponentAtNode()) и удалением компонента. В этом методе выполняется необходимый сброс: отмена таймеров, сетевых запросов и подписок, созданных ранее.

2.3.6 Refs


Рефы дают возможность получить доступ к DOM-узлам или React-элементам, созданным в рендер-методе. Обычно родительские компоненты могут взаимодействовать с дочерними только через пропсы, но иногда вам требуется императивно изменить дочерний элемент, обойдя обычный поток данных. Рефы как небольшие лазейки, которые позволяют вам обойти семантику React в тех случаях, когда обычным способом достичь требуемой функциональности невозможно. Из-за чего в случаях, когда задачу можно решить декларативным способом вы должны избегать использования рефов.

Ситуации, в которых использования рефов является оправданным:

  • Управление фокусом, выделение текста или воспроизведение медиа.
  • Императивный вызов анимаций.
  • Интеграция со сторонними DOM-библиотеками.

Рефы создаются с помощью React.createRef() и прикрепляются к React-элементам через ref атрибут. Когда атрибут ref используется с HTML-элементом, свойство current созданного рефа в конструкторе с помощью React.createRef() получает соответствующий DOM-элемент.

Когда атрибут ref используется с классовым компонентом, свойство current объекта-рефа получает экземпляр смонтированного компонента.

Нельзя использовать ref атрибут с функциональными компонентами, потому что для них не создаётся экземпляров. Для этого используйте хук useRef . Говоря проще реф это ссылка на DOM элемент либо на экземпляр смонтированного компонента.

Пример:

class RefExample extends React.Component {
    constructor(props) {
        super(props);

        this.ref = React.createRef();
    }
    handleClick = () => {
        var color = Math.floor(Math.random() * 16777215).toString(16);
        this.ref.current.style.color = `#${color}`;
    }
    render() {
        return( 
            <>
                <h1 ref={this.ref}>Hello world</h1>
                <button onClick={this.handleClick}>Click me</button>
            </>);
    }
}

В этом примере this.ref это ссылка на дом элемент h1 — Hello world. При нажатии на кнопку вызывается функция handleClick использующая ссылку на h1 через ref и рандомно изменяет цвет текста.

Узнайте больше о Рефах здесь.

2.4 ReactDOM


Если объект React из react предоставляет API для создания и произведения различных манипуляции над React объектами и компонентами, то ReactDom предоставляет API для рендеринга этих компонентов или объектов.

Под рендерингом понимается отображение элементов или компонентов на странице.

Методы, предоставляемые пакетом ReactDom:

  • render() — Рендерит React-элемент в DOM-элемент;
  • hydrate() — Рендерит React-элемент в DOM-элемент используя HTML-содержимое которого было отрендерено с помощью ReactDOMServer;
  • unmountComponentAtNode() — Удаляет смонтированный компонент React из DOM и очищает его обработчики событий и состояние;
  • createPortal() — Создаёт портал.

*Здесь нет метода findDOMNode() поскольку он считается устаревшим и не желательным для употребления. Почему его не стоит использовать вы можете прочитать тут. Также здесь нет метода flushSync(), поскольку он был добавлен недавно и ещё не был задокументирован. Также здесь не рассматриваются экспериментальные нестабильные методы методы. Такие как (unstable_renderSubtreeIntoContainer(), unstable_createPortal(), unstable_batchedUpdates(), unstable_interactiveUpdates(), unstable_createRoot(), unstable_flushControlled()). Ну и конечно же свойство __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED — лично я не хочу быть уволенным, поэтому не буду это использовать и рассматривать :D

2.4.1 Render


ReactDOM.render(element, container[, callback])

Рендерит React-элемент в DOM-элемент, переданный в аргумент container и возвращает ссылку на корневой экземпляр ReactComponent или null для компонентов без состояния.

Любые существующие дочерние элементы DOM container заменяются при первом вызове.
Другими словами — если вы рендерите в DOM узел, заведомо имеющий какие-либо дочерние элементы, то они будут удалены и заменены на element.

Повторный вызов произведёт обновление контейнера и изменит соответствующую часть DOM, чтобы она содержала последние изменения.

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

2.4.2 Hydrate


ReactDOM.hydrate(element, container[, callback])

Тоже самое что и ReactDOM.render но для случаев, когда рендеринг на клиенте основан на результатах серверного рендеринга. Что такое Server-Side Rendering я объяснять не собираюсь. Для этого уже написана отдельная статья и соответсвующий раздел документации ReactDOMServer.

React ожидает, что отрендеренное содержимое идентично на сервере, и на клиенте. Текстовые различия в контенте могут быть переписаны поверх, но вам следует рассматривать такие нестыковки как ошибки и исправлять их.

Если атрибут отдельного элемента или текстовое содержимое неизбежно отличается на сервере и клиенте (например, отметка времени), вы можете отключить предупреждение, добавив к элементу suppressHydrationWarning={true}. Он работает только на один уровень в глубину, и задумана как лазейка. Не злоупотребляйте ею.

2.4.3 UnmountComponentAtNode


ReactDOM.unmountComponentAtNode(container)

Удаляет смонтированный компонент из container и очищает его обработчики событий и состояние. Если в контейнер не было смонтировано ни одного компонента, вызов этой функции ничего не делает. Возвращает true, если компонент был размонтирован, и false если нет компонента для размонтирования. Не перепутайте, container это не React элемент который необходимо удалить, а DOM узел в котором находится компонент.

2.4.4 CreatePortal


ReactDOM.createPortal(child, container)

Порталы позволяют рендерить дочерние элементы в DOM-узел, который находится вне DOM-иерархии родительского компонента.

child — это любой React-компонент, который может быть отрендерен
container — это ссылка на DOM-элемент.

Что бы понять принцип действия порталов давайте рассмотрим простой пример:

class Welcome extends React.Component {
    render() {
        return <div>
            <p>{this.props.children}</p>
        </div>;
    }
}

render(<Welcome>Привет, мир!</Welcome>, document.getElementById("root"));

// После выполнения кода в контейнере с id root будет вставлено  <div>Привет, мир!</div>

Теперь используя портал, можно изменить то куда будет отрендерин компонент, точнее часть компонента:

class Welcome extends React.Component {
    render() {
        return <div>
            <p>{this.props.children}</p>
            {createPortal(<p>{this.props.children}</p>, document.getElementById("app-root"))}
        </div>;
    }
}

render(<Welcome>Привет, мир!</Welcome>, document.getElementById("root"));

// Теперь первый тэг <p> будет отрендерен в #root, а второй в #app-root

image

Перехват событий, всплывающих от портала к родительскому компоненту, позволяет создавать абстракции, которые не спроектированы специально под порталы. Например, вы отрендерили некий дочерний элемент. Тогда его события могут быть перехвачены родительским компонентом, вне зависимости от того, был ли этот дочерний элемент реализован с использованием порталов или без них. То есть, не смотря на то где находятся дети, родительский компонент все равно будет перехватывать события даже с учётом того что в html он и не является родительским благодаря использованию порталов.

3. Other topics


Здесь будут рассмотрены прочие темы, которые на мой взгляд не относятся к базовым понятиям React. В общем то прочитав первые два пункта 1. Getting started with React и 2. Basically React вы уже будете обладать достаточными знаниями для начала работы с React.

3.1 Lists and Keys


Одной из целей, которые преследовали авторы при разработке React это многократное использование встроенных интерфейсов. Поэтому компоненты могут напоминать шаблоны для интерфейсов. Компоненты, написанные разными людьми, должны хорошо работать вместе. Этот патерн принято называть композиция (en. Composition).

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

Давайте рассмотрим простой пример: у нас есть список друзей пользователя, который мы хотим отобразить в сайдбар. Мы можем сделать это следующим образом:

var users = ["Mike", "Bill", "Scott"];

function UsersList(props) {
    const userlist = props.children.map((users) => <p>{users}</p>);
    
    return <div>{userlist}</div>;
}

render(<UsersList>{users}</UsersList>, element);

В главе 2.3.5 Lifecycle при рассмотрение метода render() вы узнали, что данный метод может возвращать не только React элементы или компоненты, но также и массивы, фрагменты, порталы, строки, числа, Booleans или null. Таким образом вы можете вернуть массив пользователей, состоящий из элементов React. Это конечно удобно, но при возвращении массива из компонента, вы получите следующее предупреждение:
Warning: Each child in a list should have a unique «key» prop.

У каждого ребенка в списке должно быть уникальное свойство «key».

Key это специальный строковый атрибут, который нужно указывать при создании списка элементов. Он нужен для оптимизации работы React при изменении DOM с течением времени. Для большего понимания работы React при изменение DOM прочитайте статью Алгоритм сравнения. Ключи должны быть уникальны в пределах одного списка элементов. Если переписать компонент с использованием Key то он будет выглядеть следующим образом:

function UsersList(props) {
    const userlist = props.children.map((users, index) => <p key={index}>{users}</p>);
    
    return <div>{userlist}</div>;
}

На самом деле использование индекса элемента для атрибута Key, как в примере выше является плохой практикой в случаях, когда порядок элементов может поменяться потому что это может негативно сказаться на производительности и вызвать проблемы с состоянием компонента. Поэтому лучше использовать ID из ваших данных. Ключи не будут отображаться как атрибуты в DOM элементах, они являются свойствами React элементов. Вы можете убедится в этом вызвав обьект элемента в консоле с помощью element.__reactInternalInstance$235y8cxv0e8 (возможно после $ у вас будет другое число). element.__reactInternalInstance$235y8cxv0e8 — эксперементальное свойство, не опирайтесь на него при разработке.

3.2 Error Handling


При разработке больших приложений могут возникать трудности с обнаружением и исправлением целого ряда различных ошибок, например, ошибок проектирования, логических или синтаксических ошибок. Для этого в React есть различные средства, которые призваны помочь разработчикам в поиске и исправлений таких ошибок.

Ошибка JavaScript где-то в коде UI не должна прерывать работу всего приложения. Для этого были добавлены специальные методы отлавливающие ошибки JavaScript в любом месте деревьев их дочерних компонентов, для сохранения их в журнале ошибок и вывода запасного UI вместо рухнувшего дерева компонентов. Они отлавливают ошибки при рендеринге, в методах жизненного цикла и конструкторах деревьев компонентов, расположенных под ними. Объекты реализующие такие методы принято называть «Предохранители». Вы можете узнать больше о предохранителях в специальном разделе документации Error Boundaries. Работают они следующем образом: если возникают ошибки в дочерних элементах предохранителей, они вызывают специальные методы и рендерят запасной вариант вместо рухнувшего интерфейса.

JavaScript язык с динамической типизации. Такой вариант типизации сильно облегчает работу для программистов, но может привести к сложным ошибкам. Инструменты для статической типизации, такие как Flow или TypeScript, позволяют отлавливать большую часть таких ошибок ещё до исполнения кода. Кроме того, они существенно улучшают процессы разработки, добавляя авто дополнение и другие возможности. Для использования статической типизации при написании React приложений прочитайте соответствующий раздел документации Static Type Checking.

Но даже если вы не используете расширения вроде Flow и TypeScript React предоставляет встроенные возможности для проверки типов. Это проверка типов с помощью PropTypes. Для проверки на правильность переданных типов данных в пропсы компонента вам нужно использовать специальное свойство propTypes:

class PropTypesExample extends React.Component {
    render() {
        return <h1>Привет, {this.props.name}</h1>;
    }
}
  
PropTypesExample.propTypes = {
    name: PropTypes.string
};
ReactDom.render(<PropTypesExample name={5} />, element);
При этом если вместо строки передать число (как показано выше), то в консоле можно будет увидеть следующее предупреждение: Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `PropTypesExample`, expected `string`.
С версии React 15.5 React.PropTypes были вынесены в отдельный пакет. Так что используйте библиотеку prop-types
Также для обнаружения потанцеальных проблем вам может помочь Строгий режим (StrictMode). Также, как и Fragment, StrictMode не рендерит видимого UI. Строгий режим активирует дополнительные проверки и предупреждения для своих потомков.

Пример:

function Example() {
    return (
        <React.StrictMode>
            <h1>Hello, world!</h1>
        </React.StrictMode>
    );
}
render(<Example />, element); 

Используйте библиотеку React Testing Library для написания тестов. В качестве альтернативы используйте утилиту тестирования Enzyme, которая легко позволяет делать проверки, управлять, а также просматривать выходные данные React-компонентов. Также прочитайте специальный раздел документации React (Testing) что бы знать больше о тестах.

3.2.1 getDerivedStateFromError


static getDerivedStateFromError(error)

Этот метод жизненного цикла вызывается после возникновения ошибки у компонента-потомка. Он получает ошибку в качестве параметра и возвращает значение для обновления состояния.
Стоит отметить места, в которых данные методы не отловят ошибки: в обработчиках событий, асинхронном коде, серверном рендеринге, самом предохранителе. Поэтому если ошибка произойдет в дочернем компоненте, то она будет отловлена и вызваны методы static getDerivedStateFromError() и componentDidCatch().

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = { error: false };
    }
    static getDerivedStateFromError(error) {
        // Произошла ошибка!!!

        return {error: true};
    }
    render() {
        if(this.state.error) { // Произошла ошибка
            return <h1>Что-то пошло не так.</h1>; // Отображение запасного UI
        }
        // Ошибки нет
            return <Welcome>{this.props.children}</Welcome>; // Отображение того что нужно
    }
}

ReactDom.render(<App>Hello world</App>, document.getElementById("root"));

// Если в <Welcome> произойдёт исключение, то будет вызван getDerivedStateFromError

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

3.2.2 componentDidCatch


componentDidCatch(error, info)

Этот метод также, как и getDerivedStateFromError отвечает за обработку ошибок и вызывается сразу после getDerivedStateFromError. Если предназначение getDerivedStateFromError это вернуть значение для обновления состояния, то предназначение componentDidCatch это логирование ошибок. То есть в componentDidCatch вы должны писать код для журналирования информации об отловленной ошибке.

Этот метод получает два параметра:
error — перехваченная ошибка
info — объект с ключом componentStack, содержащий информацию о компоненте, в котором произошла ошибка.

Дополняя предыдущий пример методом componentDidCatch получаем следующий код:

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = { error: false };
    }
    static getDerivedStateFromError(error) {
        // Произошла ошибка!!!

        return {error: true};
    }
    componentDidCatch(error, info) {
        console.info(info.componentStack); // Вывод состояния стека и ошибки в консоль
        console.error(error); // В продакшене этих вызовов console не должно быть!!!

        logComponentStackToMyService(info.componentStack); // Метод обрабатывающий ошибку
    }
    render() {
        if(this.state.error) { // Произошла ошибка
            return <h1>Что-то пошло не так.</h1>; // Отображение запасного UI
        }
        // Ошибки нет
            return <Welcome>{this.props.children}</Welcome>; // Отображение того что нужно
    }
}
ReactDom.render(<App>Hello world</App>, document.getElementById("root"));

Методы console были добавлены только для того то бы показать из чего состоят объекты ошибки. В консоле получаем следующее:

image

Заключение


Спасибо что прочитали мою статью «Введение в React». Эта статья затрагивает основные (и не только) темы и концепции React. После прочтения данной статьи вы будете обладать хорошим фундаментом теоретических знаний о React которых будет достаточно для грамотного начала вашего первого проекта. Я надеюсь, что эта статья поможет вам в освоении React'а. Конечно для разработки настоящих проектов знаний одного только React вам будет явно недостаточно. Вы скорее всего будете использовать кучу других библиотек и пакетов для написания более сложных интерфейсов. Да и для того что бы облегчить себе работу (зачем изобретать велосипед) если кто-то уже его изобрёл. Скорее всего вы будете использовать Redux для лучшей работы с хранилищем, React Router для обеспечения URL-маршрутизации, Axios для выполнения асинхронных HTTP-запросов, GraphQL JS удобный язык запросов для API, а также огромное количество других API. Пишите в комментариях какие библиотеки используете вы.

Огромное количество Hooks API для React из GitHub.
Теги:htmljavascriptReactJSXBabelweb-разработкафреймфоркtutorial
Хабы: JavaScript Программирование Профессиональная литература ReactJS
+4
17,4k 158
Комментарии 7
Похожие публикации
Основы HTML и CSS
30 ноября 2020БесплатноНетология
Разработка приложений на Kotlin
2 декабря 202020 900 ₽Нетология
Комплексное обучение JavaScript
14 декабря 202027 000 ₽Loftschool
JavaScript Developer. Professional
24 декабря 202070 000 ₽OTUS
Vue.js Продвинутая веб-разработка
11 января 202127 000 ₽Loftschool
Лучшие публикации за сутки