Всё, что нужно знать, чтобы войти в React в 2018 году

Stas Bagretsov
14 min readAug 8, 2018

--

В этой статье вы найдете все, чтобы войти в разработку на React.

Что такое React?

React это JavaScript библиотека, которая ставит своей целью упрощение разработки визуальных интерфейсов.

Разработанный в Facebook и увидевший свет в 2013 году, он стал одним из самых распространенных примеров кода в мире, приводя в действие Facebook и Instagram, а также много много других компаний.

Перевод статьи INTRODUCTION TO REACT

👉Мой Твиттер — там много из мира фронтенда, да и вообще поговорим🖖. Подписывайтесь, будет интересно: ) ✈️

Оглавление статьи:

Что такое React?
JSX
Компоненты React
События
Декларативный подход в React
Виртуальный DOM
Context API

Основной его целью является упрощение рассуждений об интерфейсе и его состоянии в любой момент времени, разделяя UI на коллекцию компонентов.

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

Почему React так популярен?

React взял мир фронтенда штурмом. Почему?

Не такой сложный, как другие альтернативы

В то время, когда React был анонсирован, Ember.js и Angular 1.x были главным выбором, как фреймворки. Оба они навязывали слишком много конвенций в коде, делая портирование существующих приложений совершенно неудобным. React сделал выбор очень простым, при интеграции в существующий проект, потому что это то, как делали в Facebook для того, чтобы внедрить React в уже существующий код. Также, эти два фреймворка требовали слишком много, в то время как React нуждался только в одном View слое, вместо полного стека MVC.

Идеальный тайминг

В тоже время, Angular 2.x был анонсирован Google, вместе с недостатками несовместимости и основными изменениями, которые он собирался привнести. Переход с Angular 1 на 2, было похоже на переход на совершенно новый фреймворк, так что из-за этого, вместе с улучшением скорости, которые обещал React, это заставило многих разработчиков с нетерпением попробовать React.

Поддержка Facebook

Поддержка Facebook безусловно давала выгоду проекту, если бы он стал успешным, но это не гарантия, как вы можете видеть по многим неудачным open source проектам, для мира, как и от Facebook, так и от Google.

Так ли React прост?

Хоть я и сказал, что React проще, чем альтернативные фреймворки, заход в React может быть всё же затрудненным, но в основном из-за побочных технологий, которые могут быть с ним интегрированы, такие как Redux, Relay или GraphQL.

React сам по себе имеет довольно маленький API.

Нет ничего такого в React, кроме этих концепций:

Компоненты

JSX

State

Props

Далее мы рассмотрим каждый из них.

JSX

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

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

Время от времени, несколько лет, я смотрел на него и все таки начал понимать JSX и сейчас, я в основном предпочитаю его, альтернативе, которая является использованием темплейтов.

Главной пользой использования JSX, является то, вам нужно взаимодействовать только с объектом JavaScript, а не со строками темплейта.

JSX это не вставленный HTML

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

Вот, как вы определяете h1 тег включающий строку:

const element = <h1>Hello, world!</h1>

Это похоже на странный микс JavaScript и HTML, но в реальности это всё JavaScript.

То, что похоже на HTML, на самом деле синтаксический сахар для определения компонентов и их позиционирования внутри разметки.

Внутри выражения JSX, атрибуты могут вставляться очень легко:

const myId = ‘test’const element = <h1 id={myId}>Hello, world!</h1>

Вам нужно только обращать внимание, когда атрибут имеет тире (-), что конвертирует его в горбатый регистр и на эти два особые случая:

class это className

for это htmlFor

Так как то зарезервированные слова в JavaScript.

Вот пример JSX, который обертывает два компонента в тег div:

<div><BlogPostsList /><Sidebar /></div>

Тег всегда должен быть закрытым, так как это больше XML, чем HTML. Если помните времена XHTML, то было тогда похоже, но с тех времен более свободный синтаксис HTML5 все таки победил. В этом случае используется самозакрывающийся тег.

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

Компоненты React

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

В React это всё вообще очень просто: суть в том, что всё — это компонент.

Даже простые HTML теги это компонент сам по себе и они добавлены по-дефолту.

Следующие две строки равноценны, они делают одно и тоже. Одна с JSX, другая без, вставляя <h1>Hello World!</h1> в элемент с id app.

import React from ‘react’import ReactDOM from ‘react-dom’ReactDOM.render(<h1>Hello World!</h1>,document.getElementById(‘app’))ReactDOM.render(React.DOM.h1(null, “Hello World!”),document.getElementById(‘app’))

Смотрите, React.DOM выдает нам h1 компонент. Какие другие HTML теги доступны? Все из них! Вы можете проверить, что предлагает React.DOM, набрав это в консоли браузера:

Ну вы понимаете, что список будет дольше.

Встроенные компоненты хороши, но вы быстро их перерастете. Чем выделяется React, так это тем, что он позволяет нам составлять UI, составляя кастомные компоненты.

Кастомные компоненты

Есть два способа определить компонент в React.

Компонент без запоминания состояния не управляет внутренностями, а просто является функцией:

const BlogPostExcerpt = () => {return (<div><h1>Title</h1><p>Description</p></div>)}

Компонент с запоминанием состояния это класс, который управляет состоянием своих внутренних свойств:

import React, { Component } from ‘react’class BlogPostExcerpt extends Component {render() {return (<div><h1>Title</h1><p>Description</p></div>)}}

Учитывая данные примеры, они являются равноценными, так как пока что у них нет управления состоянием.

Также есть третий синтаксис, который использует ES/ES2015, без классов:

import React from ‘react’React.createClass({render() {return (<div><h1>Title</h1><p>Description</p></div>)}})

Вы редко увидите это на ES6.

Props это то, как компоненты получают свои свойства. Начиная с верхнего компонента, каждый дочерний компонент получает свои props от родителя. В компоненте без запоминания состояния, все props передаются и они доступны при добавлении props, как аругумента функции:

const BlogPostExcerpt = props => {return (<div><h1>{props.title}</h1><p>{props.description}</p></div>)}

В компоненте с запоминанием состояния, props передаются по-дефолту. Нет нужды в добавлении чего-то особенного, а так же они легко доступны как this.props в экземпляре компонента.

import React, { Component } from ‘react’class BlogPostExcerpt extends Component {render() {return (<div><h1>{this.props.title}</h1><p>{this.props.description}</p></div>)}}

Передача props дочерним компонентам это отличный способ передачи значений по всему приложению. Компонент может как хранить данные (имея состояние), так и получать их через свои props.

Все становится сложнее, когда:

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

Вам нужно получить доступ к состоянию компонента от полностью не сопоставимого компонента.

Redux традиционно очень популярен для этого и это основная причина почему он включен во многие мануалы.

Не так давно, React представил контекстное API, которое делает Redux излишним для таких простых примеров использования.

Но о context API мы поговорим немного позже.

Redux же до сих пор полезен, если:

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

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

Но теперь он не “обязателен” для каждого приложения на React.

Фрагменты

Помните как я обернул отдающиеся значения в div? Это потому, что компонент может вернуть только один элемент и если вы хотите вернуть больше одного, то вам придется обернуть его в еще один тег контейнер.

В итоге мы получаем совершенно ненужный div на выходе. Но вы можете этого избежать, начав использовать React.Fragment:

import React, { Component } from ‘react’class BlogPostExcerpt extends Component {render() {return (<React.Fragment><h1>{this.props.title}</h1><p>{this.props.description}</p></React.Fragment>)}}

У которого, кстати, есть очень удобный краткий синтаксис, который поддерживается в последних релизах:

import React, { Component } from ‘react’class BlogPostExcerpt extends Component {render() {return (<><h1>{this.props.title}</h1><p>{this.props.description}</p></>)}}

Прототипы

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

Flow и TypeScript могут тут помочь, но в React есть способ напрямую помочь с типами props и даже перед запуском кода, наши инструменты (редакторы, линтеры) могут определять то, когда мы передаем неверные значения:

import PropTypes from ‘prop-types’;import React from ‘react’class BlogPostExcerpt extends Component {render() {return (<div><h1>{this.props.title}</h1><p>{this.props.description}</p></div>)}}BlogPostExcerpt.propTypes = {title: PropTypes.string,description: PropTypes.string};export default BlogPostExcerpt

Какие типы мы можем использовать

Существуют основополагающие типы, которые мы можем принять:

PropTypes.array

PropTypes.bool

PropTypes.func

PropTypes.number

PropTypes.object

PropTypes.string

PropTypes.symbol

Мы можем принять один из двух типов:

PropTypes.oneOfType([PropTypes.string,PropTypes.number]),

Мы можем принять одно из многих значений:

PropTypes.oneOf([‘Test1’, ‘Test2’]),

Мы можем принять изначальный экземпляр класса:

PropTypes.instanceOf(Something)

Мы можем принять любой узел React:

PropTypes.node

Или даже вообще любой тип:

PropTypes.any

У массивов есть специальный синтаксис, который мы можем использовать, чтобы массив конкретного типа:

PropTypes.arrayOf(PropTypes.string)

Объекты, мы можем составлять свойства объекта с использованием:

PropTypes.shape({color: PropTypes.string,fontSize: PropTypes.number})

Обязательные свойства

Добавляя isRequired любой опции PropTypes, мы скажем React выдать ошибку в случае, если свойство не найдено:

PropTypes.arrayOf(PropTypes.string).isRequired,PropTypes.string.isRequired,

Дефолтные значения для props

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

BlogPostExcerpt.propTypes = {title: PropTypes.string,description: PropTypes.string}BlogPostExcerpt.defaultProps = {title: ‘’,description: ‘’}

Некоторые инструменты, такие как ESLint имеют возможность принудительного определения defaultProps для компонента с некоторыми propTypes, которые явно необязательны.

Как передаются props

При инициализации компонента, передача props схожа с HTML атрибутами:

const desc = ‘A description’//…<BlogPostExcerpt title=”A blog post” description={desc} />

Мы передали заголовок, как строку, а подпись, как переменную.

Children

Children это специальный prop, который содержит значение всего, что передается в body компонента, для примера:

<BlogPostExcerpttitle=”A blog post”description={desc}>Something</BlogPostExcerpt>

В этом случае, внутри BlogPostExcerpt мы могли получить доступ к “Something”, посмотрев this.props.children.

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

Помните, что иметь состояние может только компонент, основанный на классах, так что если вам нужно управлять состояние в компоненте, основанном на функции, то вам сначала нужно “обновить” его до компонента класса:

const BlogPostExcerpt = () => {return (<div><h1>Title</h1><p>Description</p></div>)}

Станет:

import React, { Component } from ‘react’class BlogPostExcerpt extends Component {render() {return (<div><h1>Title</h1><p>Description</p></div>)}}

Выставляем дефолтное состояние

В конструкторе компонента, инициализируйте this.state. Для примера BlogPostExcerpt компонент, может иметь состояние clicked:

class BlogPostExcerpt extends Component {constructor(props) {super(props)this.state = { clicked: false }}render() {return (<div><h1>Title</h1><p>Description</p></div>)}}

Получаем состояние

Получить состояние clicked можно при помощи this.state.clicked:

class BlogPostExcerpt extends Component {constructor(props) {super(props)this.state = { clicked: false }}render() {return (<div><h1>Title</h1><p>Description</p><p>Clicked: {this.state.clicked}</p></div>)}}

Меняем состояние

Состояние никогда нельзя изменять, используя:

this.state.clicked = true

Вместо этого, вам надо всегда использовать setState(), передавая его объекту:

this.setState({ clicked: true })

Объект может содержать подмножество или супермножество состояния. Изменены будут только те свойства, которые вы передаёте, а те которые будут пропущены, останутся в своём нынешнем состоянии.

Почему мне всегда надо использовать setState()

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

Инкапсулированное состояние

Родитель компонента не может сказать, является ли потомок с запоминанием состояния или же нет. Тоже самое и с потомком компонента.

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

Это ведет к одностороннему потоку данных. Unidirectional Data Flow

UNIDIRECTIONAL DATA FLOW

Состояние (State) всегда находится во власти одного компонента. Любые данные, которые затрагивают это состояние, могут повлиять только на последующие компоненты, то есть на потомков.

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

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

Перемещаем состояние вверх по древу

По причине правил Unidirectional Data Flow, если два компонента должны делить состояние, то состояние должно быть перемещено вверх к общему предку.

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

Состояние передается вниз компонентам, которым нужны эти значения через props:

class Converter extends React.Component {constructor(props) {super(props)this.state = { currency: ‘€’ }}render() {return (<div><Display currency={this.state.currency} /><CurrencySwitcher currency={this.state.currency} /></div>)}}

Состояние может быть изменено с помощью передачи функции изменения в виде prop:

class Converter extends React.Component {constructor(props) {super(props)this.state = { currency: ‘€’ }}handleChangeCurrency = (event) => {this.setState({ currency: this.state.currency ===‘€’ ? ‘$’ : ‘€’ })}render() {return (<div><Display currency={this.state.currency} /><CurrencySwitchercurrency={this.state.currency}handleChangeCurrency={this.handleChangeCurrency} /></div>)}}const CurrencySwitcher = (props) => {return (<button onClick={props.handleChangeCurrency}>Current currency is {props.currency}. Change it!</button>)}const Display = (props) => {return (<p>Current currency is {props.currency}.</p>)}

События

React дает возможность легко управлять событиями. Приготовьтесь сказать пока addEventListener.

В предыдущей статье о состояниях вы видели такой пример:

const CurrencySwitcher = (props) => {return (<button onClick={props.handleChangeCurrency}>Current currency is {props.currency}. Change it!</button>)}

Если вы хотя бы немного использовали JavaScript, то это примерно как старые простые event хендлеры в JS, за исключением того, что тут вы все определяете в JavaScript, а не в своем HTML и передаете функцию, а не строку.

Тут имена событий немного отличаются, так как в React вы используете горбатый регистр для всего, так что onclick — будет onClick, onsubmit — будет onSubmit.

Для примера, это старый HTML с JS ивентом:

<button onclick=”handleChangeCurrency()”></button>

Обработчики событий

Это негласное правило — обработчики событий должны определяться как методы в компонент классе:

class Converter extends React.Component {handleChangeCurrency = (event) => {this.setState({ currency: this.state.currency ===‘€’ ? ‘$’ : ‘€’ })}}

Все обработчики получают объект события, который привязан, кросс-бразерно, к W3C UI спецификации по событиям.

Привязка this в методах

Не забывайте привязывать методы. Методы ES6 классов по-дефолту не привязываются. Это означает то, что this не определяется до тех пор, пока вы не определите методы как стрелочную функцию:

class Converter extends React.Component {handleClick = (e) => { /* … */ }//…}

При использовании синтаксиса инициализации свойства с Babel (включенного по-дефолту в create-react-app), в противном случае вам придется привязывать вручную через конструктор:

class Converter extends React.Component {constructor(props) {super(props);this.handleClick = this.handleClick.bind(this);}handleClick(e) {}}

THE EVENTS REFERENCE

Существует множество поддерживаемых событий, вот общий список:

Буфер обмена:

onCopy

onCut

onPaste

Составление/ввод данных:

onCompositionEnd

onCompositionStart

onCompositionUpdate

Клавиатура:

onKeyDown

onKeyPress

onKeyUp

Фокусировка:

onFocus

onBlur

Форма:

onChange

onInput

onSubmit

Мышь:

onClick

onContextMenu

onDoubleClick

onDrag

onDragEnd

onDragEnter

onDragExit

onDragLeave

onDragOver

onDragStart

onDrop

onMouseDown

onMouseEnter

onMouseLeave

onMouseMove

onMouseOut

onMouseOver

onMouseUp

Выделение:

onSelect

Касание:

onTouchCancel

onTouchEnd

onTouchMove

onTouchStart

UI:

onScroll

Колёсико мышки:

onWheel

Медиа:

onAbort

onCanPlay

onCanPlayThrough

onDurationChange

onEmptied

onEncrypted

onEnded

onError

onLoadedData

onLoadedMetadata

onLoadStart

onPause

onPlay

onPlaying

onProgress

onRateChange

onSeeked

onSeeking

onStalled

onSuspend

onTimeUpdate

onVolumeChange

onWaiting

Изображение:

onLoad

onError

Анимация:

onAnimationStart

onAnimationEnd

onAnimationIteration

Переход:

onTransitionEnd

Декларативный подход в React

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

React сделал довольно популярным свой “декларативный подход” и постарался, чтобы он проник в мир фронтенда вместе с самим React.

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

К примеру, поиск элементов в DOM при помощи jQuery или DOM ивентов является итеративным подходом.

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

Виртуальный DOM

Множество уже существующих фреймворков до React, которые выходили на эту нишу, управляли DOM напрямую при каждом изменении.

“Реальный” DOM

Что такое DOM, для начала. DOM (Document Object Model) это древовидное представление структуры страницы, начинающееся с <html> тега и идущее вниз по каждому потомку, которые называются узлами.

Оно хранится в памяти браузера и напрямую ссылается на то, что вы видите на странице. У DOM есть API, которое вы можете использовать, чтобы ориентироваться в нем, получать доступ к каждому узлу, фильтровать их и модифицировать.

У API довольно знакомый синтаксис, которые вы возможно уже видели много раз, если вы не использовали абстрактный API, который представлен в JQuery и его друзях:

document.getElementById(id)document.getElementsByTagName(name)document.createElement(name)parentNode.appendChild(node)element.innerHTMLelement.style.leftelement.setAttribute()element.getAttribute()element.addEventListener()window.contentwindow.onloadwindow.dump()window.scrollTo()

React хранит копию представления DOM, для чего React рендерит: Виртуальный DOM.

Объясняем, что такое виртуальный DOM

Каждый раз, когда DOM изменяется, браузер делает две объемные операции: перерисовывает (изменения в контенте или визуальные изменения, которые не влияют на шаблон и позиционирование относительно других элементов) и совершает reflow (то есть перерасчитывает разметку части страницы — или разметку вообще всей страницы)

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

Когда вы вызываете setState() на компоненте, указывая состояние отличающееся от предыдущего, React отмечает такой компонент как грязный. Это ключевой момент: React обновляется только тогда, когда компонент явно изменяет состояние.

Что происходит дальше:

React обновляет виртуальный DOM относительно компонентов, отмеченных как грязные (с некоторыми дополнительными проверками, такими как триггеринг shouldComponentUpdate())

Запускается алгоритм сравнивания, для согласовывания изменений.

Изменяется реальный DOM

Почему виртуальный DOM полезен: группировка

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

Context API

Контекстное API было представлено для того, чтобы вы могли передавать состояние (а также обновлять его) по всему приложению, без использования props для этого.

Команда React советует придерживаться props, в том случае если у вас всего несколько уровней потомков через которые нужно передать, так как это гораздо менее затрудненный API, нежели Context API.

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

Как он работает?

Вы создаете контекст, используя React.createContext(), который возвращает Context объект:

const {Provider, Consumer} = React.createContext()

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

class Container extends React.Component {constructor(props) {super(props)this.state = {something: ‘hey’}}render() {return (<Provider value={{state: this.state}}>{this.props.children}</Provider>)}}class HelloWorld extends React.Component {render() {return (<Container><Button /></Container>)}}

Я использовал Container, как имя этого компонента, так как он будет глобальным провайдером. Вы также можете создавать контексты поменьше.

Внутри компонента, который обернут в Provider, вы используете компонент Consumer, который может использовать контекст:

class Button extends React.Component {render() {return (<Consumer>{(context) => (<button>{context.state.something}</button>)}</Consumer>)}}

Вы также можете передавать функции в значение Provider и эти функции будут использоваться Consumer для обновления состояния контекста:

class Button extends React.Component {render() {return (<Consumer>{(context) => (<button>{context.state.something}</button>)}</Consumer>)}}

Вы также можете передать функции значению Value и эти функции будут использоваться Consumer, чтобы обновить состояние контекста:

<Provider value={{state: this.state,updateSomething: () => this.setState({something: ‘ho!’}){this.props.children}</Provider>/* … */<Consumer>{(context) => (<button onClick={context.updateSomething}>{context.state.something}</button>)}</Consumer>

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

Используя несколько файлов, вы создаете контент в одном файле и импортируете его во все места, где его используете:

//context.jsimport React from ‘react’export default React.createContext()//component1.jsimport Context from ‘./context’//… use Context.Provider//component2.jsimport Context from ‘./context’//… use Context.Consumer

--

--

Stas Bagretsov

Надеюсь верую вовеки не придет ко мне позорное благоразумие. webdev/sports/books