Введение в React фреймворк
Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)
В сегодняшнем мире фреймворков Javascript философия дизайна является ключевым дифференцирующим фактором. Если вы сравните популярные JS-фреймворки, такие как EmberJS, AngularJS, Backbone, Knockout и т.д., Вы обязательно найдете различия в своих абстракциях, моделях мысли, конечно, терминологии. Это прямое следствие основополагающей философии дизайна. Но, в принципе, все они делают одну вещь, которая заключается в абстрагировании DOM таким образом, что вы не имеете прямого отношения к элементам HTML.
Я лично считаю, что фреймворк становится интересным, когда он предоставляет набор абстракций, которые позволяют использовать другой способ мышления. В этом аспекте, react, новый фреймворк JS от людей из Facebook, заставит вас переосмыслить (в некоторой степени), как вы разложите пользовательский интерфейс и взаимодействие вашего приложения. Достигнув версии 0.4.1 (на момент написания этой статьи), React предлагает удивительно простую, но эффективную модель для создания приложений JS, которые смешивают восхитительный коктейль другого типа.
В этой статье мы рассмотрим строительные блоки React и рассмотрим стиль мышления, который может показаться контр-интуитивным с первого раза. Но, как говорят React, «Дайте ему пять минут», а затем вы увидите, как этот подход станет более естественным.
Мотивы
История React началась в пределах Facebook, где она заваривалась на некоторое время. Достигнув достаточно стабильного состояния, разработчики решили открыть его несколько месяцев назад. Интересно, что сайт Instagram также поддерживается React Framework.
React приближается к задаче DOM-абстракции с несколько иной точки зрения. Чтобы понять, как это происходит, давайте быстро замаскируем методы, принятые ранее описанными фреймворками.
Обзор высокоуровневой структуры приложений JS
Шаблон проектирования MVC (Model-View-Controller) имеет основополагающее значение для разработки пользовательского интерфейса не только в веб-приложениях, но и в интерфейсных приложениях на любой платформе. В случае веб-приложений DOM является физическим представлением View. Сам DOM генерируется из текстового html-шаблона, который извлекается из другого файла, блока сценариев или предварительно скомпилированной функции шаблона. View
- это объект, который привносит текстовый шаблон в виде фрагмента DOM. Он также настраивает обработчики событий и заботится о том, чтобы манипулировать деревом DOM как часть его жизненного цикла.
Чтобы View
было полезным, оно должно отображать некоторые данные и, возможно, разрешать взаимодействие с пользователем. Данные представляют собой Model
, которая поступает из некоторого источника данных (базы данных, веб-службы, локального хранилища и т.д.). Frameworks обеспечивают способ «привязки» данных к представлению, так что изменения в данных автоматически отражаются с изменениями в представлении. Этот автоматический процесс называется привязкой данных, и есть API / методы, чтобы сделать это как можно более незаметным.
Триада MVC дополняется Controller
, который взаимодействует с представлением и моделью и управляет потоком данных (модели) в представлении View и пользовательских событиях из представления, что может привести к изменениям в модели.



Фреймворки, которые автоматически обрабатывают поток данных взад и вперед между View и Model, поддерживают внутренний цикл событий. Этот цикл событий необходим для прослушивания определенных пользовательских событий, событий изменения данных, внешних триггеров и т.д., А затем определяет, есть ли какое-либо изменение из предыдущего цикла. Если есть изменения, на обоих концах (Вид или Модель), фреймворк гарантирует, что обе они будут синхронизированы.
Что отличает React?
С помощью React View-часть триады MVC занимает заметное место и свернута в объект, называемый Component
. Компонент поддерживает неизменяемый пакет свойств, называемый props
, и state
, которое представляет пользовательский интерфейс пользователя. Компонент, генерирующий представление Component
, является довольно интересным и, возможно, причиной, по которой React выделяется по сравнению с другими фреймворками. Вместо создания физического DOM непосредственно из файла шаблона / скрипта / функции Component
создает промежуточный DOM, который является резервным для реального HTML DOM. Затем выполняется дополнительный шаг, чтобы перевести этот промежуточный DOM в настоящий HTML DOM.
В качестве части промежуточного поколения DOM Component
также присоединяет обработчики событий и связывает данные, содержащиеся в props
и state
.
Если идея промежуточного DOM звучит немного чуждо, не беспокойтесь. Вы уже видели эту стратегию, принятую языковыми режимами (aka Virtual Machines) для интерпретируемых языков. Наша собственная среда выполнения JavaScript, сначала генерирует промежуточное представление, прежде чем выплескивать нативаный код. Это справедливо и для других языков на основе VM, таких как Java, C #, Ruby, Python и т.д.
React ловко использует эту стратегию для создания промежуточного DOM до создания окончательного HTML DOM. Промежуточный-DOM - это всего лишь графический объект JavaScript и не отображается напрямую. Существует шаг перевода, который создает реальный DOM. Это основной метод, который заставляет React выполнять быстрые манипуляции с DOM.
React в глубине
Чтобы лучше понять, как React делает все это, давайте немного погрузимся в глубь; начиная с Component
. Component является основным строительным блоком в React. Вы можете составить пользовательский интерфейс вашего приложения, собрав дерево Компонентов. Каждый компонент предоставляет реализацию метода render()
, где он создает промежуточный DOM. Вызов React.renderComponent()
в корневом компоненте приводит к рекурсивному спусканию по дереву компонентов и созданию промежуточного DOM. Затем промежуточный-DOM преобразуется в реальный HTML DOM.



Поскольку создание промежуточного DOM является неотъемлемой частью компонента, React предоставляет удобное расширение на основе XML для JavaScript, называемое JSX, для построения дерева компонентов в виде набора узлов XML. Это облегчает визуализацию и объяснение DOM. JSX также упрощает ассоциацию обработчиков событий и свойств в качестве атрибутов xml. Поскольку JSX является языком расширения, для создания окончательного JavaScript есть инструмент (командная строка и встроенный браузер). Узлы XML JSX отображаются непосредственно в компонент. Стоит отметить, что React работает независимо от JSX, а язык JSX упрощает создание промежуточного DOM.
Tooling
Ядро React Framework можно загрузить с собственного веб-сайта. Кроме того, для JSX → JS-преобразования вы можете использовать встроенный браузер JSXTransformer или использовать инструмент командной строки, называемый react-tools (установленными через NPM). Для его загрузки вам понадобится установка Node.js. Инструмент командной строки позволяет предварительно скомпилировать файлы JSX и избежать перевода в браузере. Это определенно рекомендуется, если ваши файлы JSX являются большими или многочисленными.
Простой компонент
Хорошо, мы до сих пор видели много теории, и я уверен, что вы испытываете желание увидеть какой-то настоящий код. Давайте перейдем к нашему первому примеру:
/** @jsx React.DOM */ var Simple = React.createClass({ getInitialState: function(){ return { count: 0 }; }, handleMouseDown: function(){ alert('I was told: ' + this.props.message); this.setState({ count: this.state.count + 1}); }, render: function(){ return <div> <div class="clicker" onMouseDown={this.handleMouseDown}> Give me the message! </div> <div class="message">Message conveyed <span class="count">{this.state.count}</span> time(s)</div> </div> ; } }); React.renderComponent(<Simple message="Keep it Simple"/>, document.body);
Хотя этот код довольно прост, приведенный выше код действительно покрывает хорошее количество площади поверхности React:
- Мы создаем компонент Simple с помощью
React.createClass
и передаем объект, реализующий некоторые основные функции. Наиболее важным являетсяrender()
, который создает промежуточный DOM. - Здесь мы используем JSX для определения DOM, а также прикрепляем обработчик событий mousedown. Синтаксис
{}
полезен для включения выражений JavaScript для атрибутов (onMouseDown = {this.handleClick}
) и дочерние узлы (<span class = "count"> {this.state.count} </ span>
). Обработчики событий, связанные с синтаксисом {}, автоматически привязываются к экземпляру компонента. Таким образом,this
внутри функции event-обработчика относится к экземпляру компонента. Комментарий к первой строке/** @jsx React.DOM */
является сигналом для преобразования JSX для перевода в JS. Без этой строки комментариев перевода не будет.
Мы можем запустить инструмент командной строки (jsx) в режиме просмотра и автоматически компилировать изменения из JSX → JS. Исходные файлы находятся в папке /src, а вывод создается в /build.
jsx --watch src/ build/
Вот сгенерированный JS-файл:
/** @jsx React.DOM */ var Simple = React.createClass({displayName: 'Simple', getInitialState: function(){ return { count: 0 }; }, handleMouseDown: function(){ alert('I was told: ' + this.props.message); this.setState({ count: this.state.count + 1}); }, render: function(){ return React.DOM.div(null, React.DOM.div( {className:"clicker", onMouseDown:this.handleMouseDown}, " Give me the message! " ), React.DOM.div( {className:"message"}, "Message conveyed ", React.DOM.span( {className:"count"}, this.state.count), " time(s)") ) ; } }); React.renderComponent(Simple( {message:"Keep it Simple"}), document.body);
Обратите внимание, как теги <div/>
и <span/>
сопоставляются с экземплярами React.DOM.div
и React.DOM.span
.
- Теперь давайте вернемся к нашему примеру кода. Внутри
handleMouseDown
мы используемthis.props
для чтения переданного свойства message. Мы устанавливаем message в последней строке фрагмента, в вызовеReact.renderComponent()
где мы создаем компонент<Simple/>
. Цельthis.props
- хранить данные, которые были переданы компоненту. Он считается неизменным, и только компонент более высокого уровня допускается вносить изменения и передавать его по дереву компонентов. - Внутри
handleMouseDown
мы также устанавливаем некоторое пользовательское состояние сthis.setState()
для отслеживания количества раз, когда сообщение отображалось. Вы заметите, что мы используемthis.state
в методеrender()
. Каждый раз, когда вы вызываетеsetState()
, React также запускает методrender()
, чтобы синхронизировать DOM. ПомимоReact.renderComponent()
,setState()
- еще один способ принудительного визуального обновления.
Синтетические события
События, выставленные на промежуточном DOM, такие как onMouseDown
, также действуют как слой indirection, прежде чем они будут установлены на реальном DOM. Эти события, таким образом, называются Синтетические события. React принимает событийное-делегирование, которое является хорошо известным методом, и присоединяет события только на корневом уровне реального DOM. Таким образом, на реальном DOM есть только один истинный обработчик событий. Кроме того, эти синтетические события также обеспечивают уровень согласованности, скрывая разницу между браузером и элементом.
Комбинация промежуточных данных DOM и синтетических событий дает вам стандартный и последовательный способ определения пользовательских интерфейсов в разных браузерах и даже устройствах.
Жизненный цикл компонентов
Компоненты в структуре React имеют определенный жизненный цикл и воплощают машину состояний, состоящую из трех разных состояний.



Компонент оживает после установки Mounted. Результаты монтирования при просмотре рендеринга, который генерирует дерево компонентов (промежуточное DOM). Это дерево преобразуется и помещается в контейнер-узел реального DOM. Это прямой результат вызова React.renderComponent()
.
После установки компонент остается в состоянии Update. Компонент обновляется при изменении состояния с помощью setState()
или изменении props с помощью setProps()
. Это, в свою очередь, приводит к вызову render()
, который приводит DOM в синхронизацию с данными (props
+ state
). Между последующими обновлениями React будет вычислять дельту между предыдущим деревом компонентов и вновь сгенерированным деревом. Это очень оптимизированный шаг (и флагманская функция), который минимизирует манипуляции с реальным DOM.
Конечное состояние - Unmounted. Это происходит, когда вы явно вызываете React.unmountAndReleaseReactRootNode()
или автоматически, если компонент был дочерним, который больше не генерируется в вызове render()
. Чаще всего вам не приходится иметь дело с этим и просто позвольте React делать всю работу за вас.
Теперь это было бы большим недостатком, если React не сообщил бы вам, когда он переместился между состояниями Mounted-Update-Unmounted. К счастью, это не так, и есть хуки, которые вы можете переопределить, чтобы получать уведомления о изменениях жизненного цикла. Имена говорят сами за себя:
-
getInitialState()
: подготовить исходное состояние компонента componentWillMount()
componentDidMount()
componentWillReceiveProps()
-
shouldComponentUpdate()
: полезно, если вы хотите контролировать, когда рендер должен быть пропущен. componentWillUpdate()
render()
componentDidUpdate()
componentWillUnmount()
Методы componentWill*
вызываются перед изменением состояния, после чего вызывается метод componentDid*
.
Различные фичи
Внутри дерева компонентов данные должны всегда течь вниз. Родительский компонент должен установить props
дочернего компонента для передачи любых данных от родителя к ребенку. Это называется парой Owner-Owned. С другой стороны, пользовательские события (мышь, клавиатура, касания) всегда будут всплывать от ребенка до корневого компонента, если только не обрабатываются между ними.



Когда вы создаете промежуточный DOM в render(
), вы также можете назначить свойство ref
дочернему компоненту. Затем вы можете обратиться к нему от родителя, используя свойство refs
. Это показано в нижеприведенном фрагменте.
render: function(){ // Set a ref return <div> <span ref="counter" class="count">{this.state.count}</span> </div>; } handleMouseDown: function(){ // Use the ref console.log(this.refs.counter.innerHTML); },
Как часть метаданных компонента, вы можете установить начальное состояние (getInitialState()
), которое мы видели ранее в методах жизненного цикла. Вы также можете установить значения props по умолчанию с помощью getDefaultProps()
, а также установить некоторые правила проверки этих props с помощью propTypes
. Документация дает хороший обзор различных видов проверок (проверки типов, требуемые и т.д.), которые вы можете выполнить.
React также поддерживает концепцию Mixin для извлечения многоразовых элементов поведения, которые могут быть введены в разрозненные Компоненты. Вы можете передавать миксины, используя свойство mixins
компонента.
Теперь давайте реанимируем и построим более полный компонент, который использует эти функции.
Редактор формы, построенный с использованием React
В этом примере мы создадим редактор, который принимает простой DSL (Domain Specific Language) для создания фигур. Когда вы вводите данные, вы увидите соответствующий вывод сбоку, давая вам прямую обратную связь.
DSL позволяет создавать три вида фигур: эллипс, прямоугольник и текст. Каждая форма указана на отдельной строке вместе с кучей свойств стилизации. Синтаксис прост и немного заимствован из CSS. Чтобы проанализировать строку, мы используем Regex, который выглядит так:
var shapeRegex = /(rect|ellipse|text)(\s[a-z]+:\s[a-z0-9]+;)*/i;
В качестве примера, следующий набор строк описывает два прямоугольника и текстовую метку ...
// React label text value:React; color: #00D8FF; font-size: 48px; text-shadow: 1px 1px 3px #555; padding: 10px; left: 100px; top: 100px; // left logo rect background:url(react.png) no-repeat; border: none; width: 38; height: 38; left: 60px; top: 120px; // right logo rect background:url(react.png) no-repeat; border: none; width: 38; height: 38; left: 250px; top: 120px;
... генерируя вывод, показанный ниже:

Настройка
Хорошо, давайте продолжим и создадим этот редактор. Мы начнем с файла HTML (index.html
), где мы помещаем разметку верхнего уровня и включаем библиотеки и сценарии приложений. Я только показываю соответствующие части:
<body> <select class="shapes-picker"> <option value="--">-- Select a sample --</option> <option value="react">React</option> <option value="robot">Robot</option> </select> <div class="container"></div> <!-- Libraries --> <script src="../../lib/jquery-2.0.3.min.js"></script> <script src="../../lib/react.js"></script> <!-- Application Scripts --> <script src="../../build/shape-editor/ShapePropertyMixin.js"></script> <script src="../../build/shape-editor/shapes/Ellipse.js"></script> <script src="../../build/shape-editor/shapes/Rectangle.js"></script> <script src="../../build/shape-editor/shapes/Text.js"></script> <script src="../../build/shape-editor/ShapeParser.js"></script> <script src="../../build/shape-editor/ShapeCanvas.js"></script> <script src="../../build/shape-editor/ShapeEditor.js"></script> <script src="../../build/shape-editor/shapes.js"></script> <script src="../../build/shape-editor/app.js"></script> </body>
В приведенном выше фрагменте container
div содержит наш DOM, созданный React. Наши сценарии приложений включены из каталога /build
. Мы используем JSX внутри наших компонентов и наблюдателя командной строки (jsx
), помещая преобразованные JS-файлы в /build
. Обратите внимание, что эта команда наблюдателя является частью модуля NPM с react-tools
.
jsx --watch src/ build/
Редактор разбит на набор компонентов, которые перечислены ниже:
- ShapeEditor: корневой компонент в дереве компонентов
- ShapeCanvas: отвечает за создание фигур-Компонентов (Эллипс, Прямоугольник, Текст). Он содержится в ShapeEditor.
- ShapeParser: отвечает за разбор текста и извлечение списка определений фигур. Он анализирует строки за строкой с помощью регулярного выражения, которое мы видели ранее. Неверные строки игнорируются. Это не компонент, а вспомогательный объект JS, используемый ShapeEditor.
- Ellipse, Rectangle, Text: Компоненты формы. Они становятся наследниками ShapeCanvas.
- ShapePropertyMixin: предоставляет вспомогательные функции для извлечения стилей, найденных в определениях фигур. Это смешано с тремя компонентами формы, используя свойство
mixins
. - app: точка входа для редактора. Она генерирует корневой компонент (ShapeEditor) и позволяет вам выбрать образец формы из раскрывающегося списка.
Связь этих объектов показана в аннотированном дереве компонентов:



Компонент ShapeEditor
Давайте посмотрим на реализацию некоторых из этих компонентов, начиная с ShapeEditor.
/** @jsx React.DOM */ var ShapeEditor = React.createClass({ componentWillMount: function () { this._parser = new ShapeParser(); }, getInitialState: function () { return { text: '' }; }, render: function () { var shapes = this._parser.parse(this.state.text); var tree = ( <div> <textarea class="editor" onChange={this.handleTextChange} /> <ShapeCanvas shapes={shapes} /> </div>); return tree; }, handleTextChange: function (event) { this.setState({ text: event.target.value }) } });
Как следует из названия, ShapeEditor предоставляет возможность редактирования, генерируя <textarea />
и прямую обратную связь на <ShapeCanvas / <
. Он прослушивает событие onChange
(события в React всегда называются с флагом верблюда) в <textarea />
и при каждом изменении задает свойство text
у state
компонента. Как упоминалось ранее, всякий раз, когда вы устанавливаете состояние с помощью setState()
, рендер вызывается автоматически. В этом случае render()
ShapeEditor вызывается, где мы анализируем текст из состояния и перестраиваем фигуры. Обратите внимание, что мы начинаем с начального состояния пустого текста, который устанавливается в хуке getInitialState()
.
Для разбора текста в набор фигур, мы используем экземпляр ShapeParser
. Я не учитывал детали анализатора, чтобы обсуждение было сосредоточено на React. Экземпляр парсера создается в hook компонента componentWillMount()
. Это вызывается непосредственно перед монтированием компонентов и является хорошим местом для любых инициализаций до того, как произойдет первый рендеринг.
Обычно рекомендуется, чтобы вы выполнили всю сложную обработку с помощью метода render()
. Обработчики событий просто устанавливают состояние, а render()
- это концентратор для всей вашей основной логики.
ShapeEditor
использует эту идею, чтобы выполнить синтаксический анализ внутри своего render()
и пересылать обнаруженные фигуры, задав свойство shapes
ShapeCanvas
. Это то, как данные стекают в дерево компонентов, от владельца (ShapeEditor
) до принадлежащего (ShapeCanvas
).
Последнее, что нужно отметить здесь, это то, что у нас есть первый комментарий к строке, чтобы указать JSX → JS-перевод.
ShapeCanvas для создания фигур
Затем мы перейдем к компонентам ShapeCanvas и Ellipse, Rectangle и Text.
p> ShapeCanvas
довольно прост, с его основной обязанностью является генерировать соответствующие компоненты <Ellipse />
, <Rectangle />
и <Text />
из переданных в определениях фигур (this.props.shapes
). Для каждой формы мы передаем анализируемые свойства с выражением атрибута: properties = {shape.properties}
.
/** @jsx React.DOM */ var ShapeCanvas = React.createClass({ getDefaultProps: function(){ return { shapes: [] }; }, render: function () { var self = this; var shapeTree = <div class="shape-canvas"> { this.props.shapes.map(function(s) { return self._createShape(s); }) } </div>; var noTree = <div class="shape-canvas no-shapes">No Shapes Found</div>; return this.props.shapes.length > 0 ? shapeTree : noTree; }, _createShape: function(shape) { return this._shapeMap[shape.type](shape); }, _shapeMap: { ellipse: function (shape) { return <Ellipse properties={shape.properties} />; }, rect: function (shape) { return <Rectangle properties={shape.properties} />; }, text: function (shape) { return <Text properties={shape.properties} />; } } });
Другое отличие здесь в том, что наше дерево компонентов не является статичным, как в ShapeEditor. Вместо этого он динамически генерируется путем циклического перемещения по пройденным формам. Мы также показываем сообщение "No Shapes Found"
, если ничего не показывать.
Формы: эллипс, прямоугольник, текст
Все формы имеют сходную структуру и отличаются только стилем. Они также используют ShapePropertyMixin
для обработки генерации стиля.
Вот Эллипс:
/** @jsx React.DOM */ var Ellipse = React.createClass({ mixins: [ShapePropertyMixin], render:function(){ var style = this.extractStyle(true); style['border-radius'] = '50% 50%'; return <div style={style} class="shape" />; } });
Реализация для extractStyle()
предоставляется ShapePropertyMixin
.
Компонент Rectangle следует, конечно, без стиля border-radius. Компонент Text имеет дополнительное свойство, называемое value
, которое устанавливает внутренний текст для <div/>
.
Вот текст, чтобы это было ясно:
/** @jsx React.DOM */ var Text = React.createClass({ mixins: [ShapePropertyMixin], render:function(){ var style = this.extractStyle(false); return <div style={style} class="shape">{this.props.properties.value}</div>; } });
Связывание всего вместе с App.js
app.js
- это то, где мы собираем все это вместе. Здесь мы создаем корневой компонент, ShapeEditor
, а также предоставляем поддержку для переключения между несколькими образцами. Когда вы выбираете другой образец из раскрывающегося списка, мы загружаем некоторый предопределенный текст в ShapeEditor
и заставляем ShapeCanvas
обновляться. Это происходит в методе readShapes()
.
/** @jsx React.DOM */ var shapeEditor = <ShapeEditor />; React.renderComponent( shapeEditor, document.getElementsByClassName('container')[0] ); function readShapes() { var file = $('.shapes-picker').val(), text = SHAPES[file] || ''; $('.editor').val(text); shapeEditor.setState({ text: text }); // force a render } $('.shapes-picker').change(readShapes); readShapes(); // load time
Чтобы реализовать творческую сторону, вот робот, созданный с помощью редактора Shape:

И это для вас!
Уф! Это была довольно длинная статья, и, достигнув этого момента, у вас должно быть чувство достижения!
Мы рассмотрели здесь много концепций: интегральная роль компонентов во фреймворке, использование JSX для легкого описания дерева компонентов (aka intermediate-DOM), различные хуки для подключения к lifecyle компонента, использование state
и props
для управления процессом визуализации, использование Mixins для исключения повторного использования и, наконец, вытягивания всего этого вместе с примером редактора Shape.
Я надеюсь, что эта статья даст вам достаточно стимула для создания нескольких приложений React для себя. Чтобы продолжить свое исследование, здесь несколько удобных ссылок: