Изоморфные приложения. Взгляд в будущее с React
В разработке программного обеспечения все часто возвращается на круги своя. Так, например, на заре развития Интернета серверы подгружали контент сразу же на сторону клиента. В последнее же время, с разработкой современных веб-фреймворков, таких как AngularJS и Ember, мы видим тенденцию к обработке запросов на стороне клиента и использованию сервера только для API. Однако, это далеко не единственная тенденция. Сейчас происходит медленное возвращение или, скорее, слияние этих двух архитектур.
Что такое React?
Если использовать определение с официального сайта, то
“React — это JavaScript библиотека, которая используется для создания пользовательских интерфейсов”.
С помощью React можно создавать компоненты для многоразового использования в веб-разработке. Причем, компоненты создаются легко и просто. В этом, собственно, и есть цель React.
Что делает React особенным?
React очень быстро стал популярен среди JavaScript разработчиков, и на это есть целый ряд причин. Одна из них — React был разработан Facebook и теперь им активно продвигается и используется. Это значит, что разработчики, работающие с Facebook, то же активно используют React: программируют, чинят баги, разрабатывают новые функциональности и т.п.
Другая причина быстрой популярности React состоит в том, что он сильно отличается от AngularJS, Backbone.js, Ember, Knockout и других широко известных MV* JavaScript фреймворков, которые появились как грибы после дождя за последние несколько лет. Большинство этих фреймворков работает по схеме двойного связывания с DOM и его обновлении в зависимости от событий. Для их использования наличие DOM обязательно, поэтому, когда Вы работаете с одним из этих фреймворков и хотите провести рендеринг разметки на сервере, Вам придется использовать PhantomJS или что-то аналогичное ему.
Виртуальный DOM
Если рассмотреть аббревиатуру MVC (model-view-controller), то React — скорее, можно отнести к “V”, то есть к “view”. Однако, React выполняет функцию view отлично от других MV* фреймворков. Например, он не имеет ничего общего с Handlebars, Underscore и AngularJS шаблонами. Работа React основана на концепте “виртуального DOM”. React держит виртуальный DOM в памяти и каждый раз, когда в него вносят изменения, быстро их дифференцирует, сводит в одно обновление и посылает целый пакет данных на настоящий DOM.
У такого подхода есть целый ряд преимуществ, главное из которых заключается в существенном улучшении производительности. Вам не надо постоянно обновлять DOM, как в других JavaScript фреймворках. Кроме того, React может обрабатывать запросы как на стороне сервера, так и на стороне клиента без особых проблем.
React использует метод под названием React.renderToString(). Он позволяет передавать компонент, который, в свою очередь, обрабатывает его вместе с используемым дочерним элементом и возвращает код. Вы можете просто взять этот HTML код и отправить на клиентскую сторону.
Пример
Следующие компоненты построены на JSX. С первого взгляда может показаться, что JSX похож на гибрид HTML и JavaScript:
var HelloWorld = React.createClass({
displayName: "HelloWorld",
render() {
return (
<h1>Hello {this.props.message}</h1>
);
}
});
React.render(<HelloWorld message="world" />, document.body);
Теперь Вам надо пропустить (или транспайлить) .jsx через webpack, grunt, gulp или альтернативный движок и получить на выходе JavaScript код, похожий на этот:
var HelloWorld = React.createClass({
displayName: "HelloWorld",
render: function() {
return (
React.createElement("h1", null, "Hello ", this.props.message)
);
}
});
React.render(React.createElement(HelloWorld, {message: "world"}), document.body);
Вот такой код мы получаем с помощью HelloWorld.jsx. Как видите, это обычный JavaScript. Конечно можно возразить, что смешивать HTML и JavaScript нельзя, что это ошибка; однако, так кажется только на первый взгляд. Поработав с React, понимаешь, что близость компонентов разметки позволяет писать код быстрее и делать его более объемным и цельным. Получается, что Вам не приходится постоянно переключаться между HTML и JavaScript файлами — и весь код для разрабатываемого компонента находится в одном месте.
React.render прикрепляет компонент HelloWorld к body. Очевидно, что туда можно добавить любой элемент. Он вызывает метод render компонента, и следовательно результат добавится в DOM внутри тега body.
В любом компоненте React, передали вы атрибуты или нет — например, HelloWorld message=”world”- у Вас будет доступ к this.props компонента. Поэтому, в компоненте HelloWorld, this.props.message будет world. Так что, обратите внимание на следующую часть кода с JSX:
return (
<h1>Hello {this.props.message}</h1>
);
Вы заметите, что HTML обернут в круглые скобки. Затем, this.props.message надо обернуть в фигурные скобки, которые позволят получить доступ к компоненту через this.
Кроме того, у каждого компонента есть доступ к его “состоянию”. В React каждый компонент может управлять своим состоянием с помощью нескольких простых API методов, таких как getState, setState, getInitialState. Последний используется при инициализации компонента. Когда бы ни менялось состояние, метод render проведет рендеринг компонента. Например:
var Search = React.createClass({
getInitialState() {
return {
search: ""
};
},
render() {
return (
<div className="search-component">
<input type="text" onChange={this.changeSearch} />
<span>You are searching for: {this.state.search}</span>
</div>
);
},
changeSearch(event) {
var text = event.target.value;
this.setState({
search: text
});
}
});
React.render(<Search />, document.body);
В примере выше, функция getInitialState возвращает простой литерал объекта, в котором содержится первоначальное состояние компонента.
Функция render возвращает JSX для наших элементов, значит input и span, оба обвернуты в div. Не забывайте, в JSX только один элемент может быть возвращен в качестве родителя. Другими словами, Вы не можете возвратить div два раза; Вы можете возвратить только один элемент с множеством дочерних.
Обратите внимание на onChange={this.changeSearch}. Эта команда позволяет компоненту запустить функцию changeSearch при вводе данных.
Функция changeSearch получает event, запущенный событием DOM, и получает введенный текст. Затем, мы делаем запрос на setState и передаем текст, что запустит render, и {this.state.search} отобразит появившиеся изменения.
В React можно работать с множеством разных API. Однако, при работе на высоком уровне описанного выше способа будет достаточно для создания простых компонентов.
Изоморфный JavaScript
С помощью React, мы можем создавать так называемые “изоморфные” приложения. Слово “изоморфный” уже успело набрать популярность в 2015 году. Если говорить простым языком, “изоморфность” состоит в том, что мы можем использовать один и тот же код как на стороне клиента, так и на стороне сервера. Очевидно, что у такого подхода есть масса преимуществ.
Избавляемся от FOUC
При использовании AngularJS, Ember и SPA архитектуры, когда пользователь первый раз попадает на страницу, происходит подгрузка всех ее элементов. В SPA приложениях загрузка может занять несколько секунд, и большинство пользователей сегодня ожидает не менее чем 2 секунды. Пока контент подгружается, рендеринг страницы не начинается. Эта задержка называется FOUC (flash of unstyled content). Одним из главных преимуществ разработки изоморфным способом является то, что скорость подгрузки элементов значительно ускоряется за счет одновременного рендеринга на стороне сервера и на стороне клиента.
Получается, что задача изоморфного приложения — не заменить привычный серверный API, а избавиться от потока нестилизованного контента, тем самым улучшив опыт взаимодействия пользователей со страницей (а их запросы постоянно растут).
Одинаковый код
Солидным преимуществом изоморфных приложений является возможность использования одного и того же кода на стороне сервера и на стороне клиента. Просто создайте нужные Вам компоненты и они будут работать и там, и там. В большинстве других систем, таких как Rails, Asp.NET MVC, у Вас есть erb и cshtml для рендеринга на стороне сервера. Кроме того, Вам надо использовать такие шаблоны, как Handlebars или Hogan.js, которые часто дублируют логику. В React же одинаковые компоненты одинаково хорошо работают и на стороне сервера, и на стороне клиента.
Улучшение в процессе
Рендеринг на стороне сервера позволяет быстро переслать необходимый клиенту для просмотра сайта скелет HTML. Затем, Вы можете улучшить картинку, обработав больше элементов на стороне клиента.
Как правило, обеспечить одинаковый комфорт от просмотра страницы или пользования приложением для пользователя с простейшей “раскладушкой” из Африки и пользователя с новейшим McBook Pro с Retina дисплеем и подключением к последнему 4k монитору — занятие не из простых.
В React все несколько сложнее, чем простая передача разных компонентов. Когда Вы обрабатываете компоненты React на стороне сервера и отправляете скелет HTML кода клиенту, React на стороне клиента находит аналогичный HTML код и прикрепляет контейнеры с событиями к имеющимся элементам. Вуаля!
Это означает, что достаточно отправлять только ту часть HTML кода, которая необходима для рендеринга страницы. Все дополнительные элементы могут быть извлечены и обработаны на стороне клиента. Вы получаете преимущество в виде быстрой загрузки со стороны сервера, а также в виде возможности реиспользования одних и тех же компонентов.
Создаем изоморфное приложение на Express
Express — один из самых популярных Node.js веб-серверов. Поднять и запустить приложение на Express с помощью React не проблематично.
Добавление React рендеринга на Express состоит из нескольких шагов. Сначала надо добавить node-jsx и react в проект следующим образом:
npm install node-jsx --save
npm install react --save
Далее, надо создать базовый app.jsx файл в директории public/javascripts/components. Для этого нам понадобится компонент Search, который мы использовали ранее:
var React = require("react"),
Search = require("./search");
var App = React.createClass({
render() {
return (
<Search />
);
}
});
module.exports = App;
Здесь нам понадобится react и компонент Search.jsx. В методе render, App компонента, мы можем просто использовать компонент с Search.
Затем надо добавить следующий код в один из роутеров, в котором Вы планируете провести рендеринг с помощью React:
require("node-jsx").install({
harmony: true,
extension: ".jsx"
});
Это позволит require использовать .jsx файлы. В противном случае, Node.js не поймет, как их нужно парсить. Опция harmony позволяет использовать компоненты ECMAScript 6.
Затем, необходимо запросить компонент и направить его в React.createFactory, которая возвратит функцию, с помощью которой Вы сможете вызвать компонент:
var React = require("react"),
App = React.createFactory(require("../public/javascripts/components/app")),
express = require("express"),
router = express.Router();
Затем, в самом маршруте пропишите React.renderToString и передайте компонент:
router.get("/", function(req, res) {
var markup = React.renderToString(
App()
);
res.render("index", {
markup: markup
});
});
Наконец, получите выходные данные по разметке во вкладке просмотра:
<body>
<div id="content">
{{{markup}}}
</div>
</body>
Это все, что касается кода по серверной части. Теперь давайте посмотрим, что нужно сделать на стороне клиента.
Webpack
Webpack — это JavaScript упаковщик, который позволяет собрать статические объекты (JavaScript, CSS, изображения и т.п.) в один файл, а также обработать файлы с помощью различных загрузчиков. Вы можете написать JavaScript код, используя CommonJS или синтаксис AMD модулей.
Для React .jsx файлов Вам надо будет немного сконфигурировать файл webpack.configure для компиляции всех jsx компонентов.
Начать работать с Webpack очень просто:
npm install webpack -g # Install webpack globally
npm install jsx-loader --save # Install the jsx loader for webpack
Затем, надо создать файл webpack.configure.js.
var path = require("path");
module.exports = [{
context: path.join(__dirname, "public", "javascripts"),
entry: "app",
output: {
path: path.join(__dirname, "public", "javascripts"),
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.jsx$/, loader: "jsx-loader?harmony"}
]
},
resolve: {
// You can now require('file') instead of require('file.coffee')
extensions: ["", ".js", ".jsx"],
root: [path.join(__dirname, "public", "javascripts")],
modulesDirectories: ["node_modules"]
}
}];
А теперь рассмотрим вышепредставленный код. Здесь мы имеем:
- context. Это — корень Ваших JavaScript файлов.
- entry. Это — главный файл, который будет подгружать остальные файлы с помощью require в CommonJS по умолчанию.
- output. Дает команду Webpack выдать код единым файлом, используя цепочку public/javascripts/bundle.js.
Объект module позволяет настроить загрузчики. Загрузчик же позволяет провести тестирование расширений файла, а затем передать его через загрузчик. Для CSS, Sass, HTML, CoffeeScript и JSX существует много загрузчиков, но в данном случае нам нужен всего один — jsx-loader?harmony. Вы можете прикрепить такие опции, как “строка запроса”, к имени загрузчика. В данном случае, ?harmony позволяет использовать синтаксис ECMAScript 6 в наших модулях. test дает команду Webpack передавать любой файл c расширением .jsx в jsx-loader.
В resolve мы видим несколько иную картину. Во-первых, extensions дает команду Webpack пропускать файлы с расширениями определенных типов, когда мы прописываем require. Благодаря этому, мы можем использовать require(“./file”), а не require (“./file.js”). К тому же, мы задаем root, который, по сути, является просто корнем, из которого будут извлекаться файлы. Наконец, мы разрешаем Webpack извлекать модули из директории node_modules, используя modulesDirectories. Так, мы можем установить, например, Handlebars с помощью npm install handlebars и require (“handlebars”), так же, как и в приложениях на Node.js.
Код на стороне клиента
Для public/javascripts/app.js нам понадобится тот же самый App компонент, который мы использовали в Express:
var React = require("react"),
App = React.createFactory(require("components/app"));
if (typeof window !== "undefined") {
window.onload = function() {
React.render(App(), document.getElementById("content"));
};
}
Проверим, что мы находимся в браузере с помощью команды typeof window !==”undefined”. Затем, прикрепим к onload события окна, вызовем React.render и осуществим передачу в App(). Далее нам понадобится DOM элемент для подгрузки. Это должен быть тот же самый элемент, который мы использовали в разметке React на стороне сервера, то есть #content.
Компонент Search из примера выше был обработан на сервере и отправлен клиенту. React клиентской стороны принимает прошедшую рендеринг разметку и прикрепляет к нему контейнеры событий! Это значит, что мы сможем без проблем просматривать страницу, пока JavaScript еще подгружается.
Весь представленный в статье код доступен на GitHub.
Вывод
Веб архитектура развивается циклами. Сначала рендеринг происходил только на стороне сервера с последующей отправкой данных клиенту. Затем, появился JavaScript, и мы стали использовать его для простых операций на странице. Наконец, JavaScript развился настолько, что стало возможным использовать его для создания больших приложений, которые будут осуществлять рендеринг на стороне клиента и использовать сервер для извлечения данных через API.
В 2015 же году, мы начали понимать, что имеем в распоряжении мощные серверы, которые имеют достаточно процессоров и памяти для обработки практически любых данных. Изоморфный подход к разработке приложений может дать нам лучшее из двух миров: мы можем использовать JavaScript как на стороне сервера, так и на стороне клиента, что повышает скорость загрузки и позволяет пользователям видеть информацию на странице быстрее по мере того, как происходит подгрузка дополнительных элементов на стороне клиента.
React – один из первых фреймворков, которых без сомнения уже множество, что реализует такой вид поведения. Разработчики Ember уже тоже работают над изоморфными приложениями. Уверен, что наблюдать за развитием этих фреймворков будет очень интересно!
Об авторе
Джонатан Кример (Jonathan Creamer) — старший веб-разработчик в компании Lonely Planet. Увлекается JavaScript, в особенности архитектурой веб-приложений и разработкой на Node.js. Кроме того, у него есть богатый опыт работы с ASP.NET MVC, Ruby on Rails и ColdFusion. Джонатан занялся веб-разработкой в 13 лет, когда пошел на работу в один из стартапов в городе Франклин, штат Теннесси. Затем он пошел учиться на разработчика в Middle Tennessee State University. В настоящее время проживает в городе Мерфрисборо, штат Теннесси, вместе с женой и дочерью.
Над переводом работали: greebn9k (Сергей Грибняк), seninrom(Роман Сенин), silmarilion(Андрей Хахарев)
Singree
Комментарии (13)
alemiks
05.06.2015 18:01+4во всех подобных статьях должно быть:
>>>Один пацан писал все на JavaScript, и клиент, и сервер, говорил что нравится, удобно, читабельно. Потом его в дурку забрали, конечно.a_l
05.06.2015 18:11+5В реакте «магии» как раз по минимуму, отчасти поэтому он многим JS-разработчикам нравится.
AlexeyFrolov
06.06.2015 13:03тут написано именно про то, что я пытался объяснить в radio-t ( www.radio-t.com/p/2015/05/23/podcast-445, примерно с 23-й минуты ). За 20 минут непосвященным во фронтенды было тяжко донести эти вещи…
AlexeyFrolov
06.06.2015 13:12вот именно про это
В разработке программного обеспечения все часто возвращается на круги своя. Так, например, на заре развития Интернета серверы подгружали контент сразу же на сторону клиента. В последнее же время, с разработкой современных веб-фреймворков, таких как AngularJS и Ember, мы видим тенденцию к обработке запросов на стороне клиента и использованию сервера только для API. Однако, это далеко не единственная тенденция. Сейчас происходит медленное возвращение или, скорее, слияние этих двух архитектур.
Houston
06.06.2015 13:55Позволю себе придраться, FOUC это нечто другое, и применено не к месту
A flash of unstyled content (FOUC) is an instance where a web page appears briefly with the browser's default styles prior to loading an external CSS stylesheet, due to the web browser engine rendering the page before all information is retrieved. The page corrects itself as soon as the style rules are loaded and applied; however, the shift is quite visible and distracting.
Wikipedia
kulakowka
06.06.2015 15:54А есть у кого нибудь опыт прикручивания React.js + Babel (ES6) к Sails.js? Было бы интересно взглянуть на вашу реализацию.
DmitryKoterov
07.06.2015 15:58-2Иногда, глядя на некоторые современные тормозные, глючные, неудобные и теряющие введенные данные «single-page applications», думаешь: «уж лучше бы они по старинке рендерили все на сервере, постили формы и перезагружали страницу, без JavaScript». Равно как и глядя и на «оптимизированные для мобильных устройств» сайты, в котором половина функций отсутствует, а вторая глючит: «лучше бы никакой оптимизации они не делали и показывали десктопную версию». Особенно беда с этим в интернет-банках, выкатывающих все «новые и новые» версии и становящиеся «все лучше и лучше».
Есть, правда, и исключения, и даже примеры высшего пилотажа (ИМХО — gmail из их числа), но в наши дни это все-таки скорее исключения.greebn9k Автор
08.06.2015 10:32Позволю себе не согласится. Если Вы умеете готовить SPA, то описанные Вами проблемы проявляются редко. Основное проседание по латенси, как правило, уходит на HTTP запросы. Так что в контексте «лучше бы они рендерили все на сервере», можно смело, к времени запроса, добавлять время на серверный рендеринг.
DmitryKoterov
08.06.2015 17:04Если уметь готовить, то проблем нет, и SPA — это хорошо. Но только вот умеют их готовить очень мало компаний сейчас, как мне кажется.
pragmadash
Это скорее:
и уж точно не:
Ваш перевод, мягко говоря, не соответствует содержанию оригинала и вводит в заблуждение читателей.
greebn9k Автор
Исправили. Спасибо.
Bronx
Скорее «один из первых в уверенно растущем множестве фреймворков».
Ваш перевод ещё дальше от оригинала, потому что «what are sure to be many» — это не «без сомнения уже множество», а «несомненно *будет* множество».
pragmadash
Вы действительно считаете, что мой перевод дальше от оригинала?
Если так, то я с вами не могу согласиться. Я имел ввиду, что весь пост в целом уж очень далек от оригинала (по крайней мере в первой его версии до редактирования).