Всё, что нужно знать, чтобы войти в React в 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