Готовь сани летом а телегу зимой
Мало кто на перед задумывается о том, как готовое приложение будет работать на боевом сервере. Обычно эти вопросы решаются в самом конце, когда код уже написан, и пути назад нет. Именно поэтому прежде чем заняться изучением самой библиотеки, определитесь с вопросами компиляции кода вашего будушего творения. Варианта тут два. Для учебы, или скажем, демоверсии сайта, подойдет вариант компиляции на стороне клиента. Ничего делать не надо, все за вас сделает браузер, “на лету” так сказать. Но вот для готового продукта, я бы посоветовал настроить компиляцию на стороне сервера. Благо инструментов для этого предостаточно. Тут вам и Babel , и NodeJS или Webpack.
Детство, детство ты куда ушло
Ну вот, с вопросами компиляции разобрались. Перейдем к изучению? Нет, еще рано. React реализует модульный подход для построения приложений. Что это значит? Представьте себе конструктор. Не тот конструктор, что внутри класса, а простой детский конструктор. Точно так же, как из маленьких блоков в детстве вы строили свои шедевры, вы будете строить приложение из React компонентов. Так играть даже интересней, поскольку компоненты создаете тоже вы сами. Прелесть модульного подхода заключается в том, что создав и протестировав такой компонент один раз, вы легко можете использовать его и в других своих приложениях. Поэтому мой вам совет: создавайте отдельные файлы для каждого из них, а потом просто подключайте туда, куда надо. Вроде все просто, но это не совсем так. Приложение получиться пустым, мертвым, если его компоненты не будут “общаться” между собой. А это как раз и есть самое сложное и интересное.
Ну дайте же мне поработать!
Вижу, что ваше желание учиться тает на глазах. Поэтому открываем документацию и переходим от слов к делу. Все, что нам нужно для учебы, находиться на официальном сайте библиотеки. Правда информация структурирована плохо. Помочь вам не потеряться в этом хаосе — вот главная задача этой статьи.
Как вы уже поняли, основной задачей при разработке приложений на React, является разбивание страницы на блоки и создание компонентов, которые реализовывали бы функционал каждого из них.
Для начала создайте «статическую» версию вашего компонента. Очень рекомендую обратить внимание на JSX.
var LoginForm = React.createClass({
render: function() {
return (
<form id="login-form">
<input type="text" id="login" placeholder="login" />
<input type="password" id="password" placeholder="password" />
<button type="submit">Login</button>
</form>
)
}
});
React.render( <LoginForm />, document.getElementById('example'));
Оценили преимущества JSX синтаксиса? Тогда идем дальше. Добавим немного «интерактивности». Интерфейс компонента будет перерисовываться автоматически при изменении каких-либо данных внутри этого компонента. К ним относятся:
- State (состояние) — набор данных, которые отражают состояние компонента в конкретный момент времени.
- Props (свойства) — данные, передаваемые компоненту через атрибуты.
Поэтому все сводится к банальному изменению состояния или свойств в ответ на действия пользователя.
var LoginForm = React.createClass({
getInitialState: function(){
/* начальное состояние компонента */
return { errorCode: 0, errorMessage: '', loginActions: [] };
},
doLogin: function(event) {
event.preventDefault();
var successLogin = (Math.random() >= 0.5) ? true : false;
var actions = this.state.loginActions;
if (!successLogin) {
actions.push('Login failure');
/* изменяем состояние компонента при возникновении ошибки */
this.setState({errorCode: 1, errorMessage: 'Error while login.', loginActions: actions});
}
else {
actions.push('Login success');
this.setState({errorCode: 0, errorMessage: '', loginActions: actions});
}
},
render: function() {
/* учитываем состояние компонента при отрисовке */
var errorMessage = (this.state.errorCode > 0) ? this.state.errorMessage : '';
var errorStyle = (this.state.errorCode > 0) ? {display: 'block'} : {display: 'none'};
return (
<div>
<form id="login-form">
<div>
<input type="text" id="login" placeholder="login" />
<input type="password" id="password" placeholder="password" />
</div>
<div>
<button type="submit" onClick={this.doLogin}>Login</button>
</div>
<div style={errorStyle}>
<span style={{color: '#d9534f'}}> {errorMessage}</span>
</div>
</form>
<div className="actions-list">
/* передаем данные потомку через атрибут actions */
<ActionsList actions={this.state.loginActions} />
</div>
</div>
)
}
});
var ActionsList = React.createClass({
render: function() {
/* данные, полученые от родителя доступны через this.props */
return (
<ol>
{
this.props.actions.map(function(action) {
return <li>{action}</li>;
})
}
</ol>
)
}
});
React.render( <LoginForm />, document.getElementById('example'));
Как вы уже поняли, React значительно отличается от других библиотек. Поэтому нет ничего удивительного в том, что при работе с элементами формы тоже есть свои особенности, которые могут добавить вам изрядную долю седых волос на голове. Элементы формы делятся на два типа:
- Не контролируемые — элементы, для которых свойство value не установлено.
- Контролируемые — элементы, в которых установлено свойство value.
Давайте установим начальное значение для элементов ввода, и попробуем ввести что-нибуть:
<input type="text" id="login" placeholder="login" value=”admin” />
<input type="password" id="password" placeholder="password" value=”admpass” />
Как видим, у нас ничего не получилось. Теперь React «контролирует» эти элементы и нам нужно писать для них собственые обработчики изменений. Представляеке сколько это работы, писать функции-обработчики для каждого из контролируемых елементов? Мама не горюй! Но добрые дяденьки из Facebook не оставили нас в беде и добавили в React возможность использовать примеси (mixins). Да еще и несколько хороших дополнений (addons) подкинули.
var LoginForm = React.createClass({
/* подключаем примеси */
mixins: [React.addons.LinkedStateMixin],
getInitialState: function(){
return { errorCode: 0, errorMessage: '', loginActions: [], defaultLogin: 'admin', defaultPassword: 'password' };
},
doLogin: function(event) {
event.preventDefault();
var successLogin = (Math.random() >= 0.5) ? true : false;
var actions = this.state.loginActions;
if (!successLogin) {
actions.push('Login failure');
this.setState({errorCode: 1, errorMessage: 'Error while login.', loginActions: actions});
}
else {
actions.push('Login success');
this.setState({errorCode: 0, errorMessage: '', loginActions: actions});
}
},
render: function() {
var errorMessage = (this.state.errorCode > 0) ? this.state.errorMessage : '';
var errorStyle = (this.state.errorCode > 0) ? {display: 'block'} : {display: 'none'};
return (
<div>
<form id="login-form">
<div>
/* используем функцию из примеси и спецыальный атрибут valueLink */
<input type="text" ref="login" placeholder="login" valueLink={this.linkState('defaultLogin')} />
<input type="password" ref="password" placeholder="password" valueLink={this.linkState('defaultPassword')} />
</div>
<div>
<button type="submit" onClick={this.doLogin}>Login</button>
</div>
<div style={errorStyle}>
<span style={{color: '#d9534f'}}> {errorMessage}</span>
</div>
</form>
<div className="actions-list">
<ActionsList actions={this.state.loginActions} />
</div>
</div>
)
}
});
var ActionsList = React.createClass({
render: function() {
return (
<ol>
{
this.props.actions.map(function(action) {
return <li>{action}</li>;
})
}
</ol>
)
}
});
React.render( <LoginForm />, document.getElementById('example'));
Если вы думаете, что «сюрпризов» больше не осталось, то очень сильно ошибаетесь. Вот вам задачка: как организовать двунаправленный обмен данными между компонентами? Ведь свойства передаются только в одном направлении — от отца к потомкам. А наоборот? Как потомок может влиять на данные своего родителя? Очень просто:
var LoginForm = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function(){
/* мы хотим, чтобы потомок мог изменять состояние родителя, в часности, очищать масив loginActions */
return { errorCode: 0, errorMessage: '', loginActions: [], defaultLogin: 'admin', defaultPassword: 'password' };
},
clearActionList: function() {
/* для этого в родителе создаем функцию, которая очищает loginActions */
this.setState({loginActions: []});
},
doLogin: function(event) {
event.preventDefault();
var successLogin = (Math.random() >= 0.5) ? true : false;
var actions = this.state.loginActions;
if (!successLogin) {
actions.push('Login failure');
this.setState({errorCode: 1, errorMessage: 'Error while login.', loginActions: actions});
}
else {
actions.push('Login success');
this.setState({errorCode: 0, errorMessage: '', loginActions: actions});
}
},
render: function() {
var errorMessage = (this.state.errorCode > 0) ? this.state.errorMessage : '';
var errorStyle = (this.state.errorCode > 0) ? {display: 'block'} : {display: 'none'};
return (
<div>
<form id="login-form">
<div>
<input type="text" ref="login" placeholder="login" valueLink={this.linkState('defaultLogin')} />
<input type="password" ref="password" placeholder="password" valueLink={this.linkState('defaultPassword')} />
</div>
<div>
<button type="submit" onClick={this.doLogin}>Login</button>
</div>
<div style={errorStyle}>
<span style={{color: '#d9534f'}}> {errorMessage}</span>
</div>
</form>
<div className="actions-list">
/* и передаем эту функцию потомку через атрибут, точно так же, как и данные */
<ActionsList actions={this.state.loginActions} clearActions={this.clearActionList} />
</div>
</div>
)
}
});
var ActionsList = React.createClass({
render: function() {
return (
<div>
/* здесь мы можем изменять состояния родителя, вызывая соответствующую функцию - this.props.clearActions */
<button onClick={this.props.clearActions}> Clear list </button>
<ol>
{
this.props.actions.map(function(action) {
return <li>{action}</li>;
})
}
</ol>
</div>
)
}
});
React.render( <LoginForm />, document.getElementById('example'));
Делу время, потехе час!
Ну вот. Теперь действительно все. Первый этап пройден и я рад приветствовать вас в рядах новоиспеченных реакторов. Стоит ли ReactJS потраченного на него времени — каждый решает для себя. Пройдено только пол пути. Для кого-то эта дорога была легкой, для кого-то — не очень. Кто-то пойдет дальше, а кто-то остановиться. И я очень надеюсь, что моя статья будет хорошим подспорьем новичкам.
Комментарии (59)
prog666
22.10.2015 12:00+1Если уж все равно приходится использовать browserify или webpack то можно было бы писать на es2015/es6, babel (babelify) отлично понимает jsx и в самой документации реакта есть примеры как можно использовать нативные классы в создании компонентов. Если интересно могу показать примеры gulpfile для сборки.
VasilioRuzanni
22.10.2015 14:36+1Добавлю, что Babel не только понимает JSX, но и сама команда React'а даже отказалась от своих наработок и рекомендует использовать Babel для трансформации.
andreysmind
22.10.2015 13:16Вот вам задачка: как организовать двунаправленный обмен данными между компонентами?
Может лучше сразу к Flux привыкать?
Вроде даже в Angular 2 от двунаправленного обменами данных решили отказаться?VasilioRuzanni
22.10.2015 14:55И в Angular 2, и в Ember 2 тоже (и там, и там — плотно советуясь с командой React), и на Angular 1 тоже в итоге для больших приложений получается удобнее писать (субъективно), когда есть явная передача данных «туда» и «обратно».
Однако, часто бывает удобен и 2-way-binding, но надо понимать, когда его лучше использовать (в Aurelia он хорошо сделан и лишен недостатков оного в Angular 1) — тут создатели разных фреймворков вроде как сошлись во мнении, что правильный 2-way-binding — это сахар над one-way-binding для отдельных случаев (именно такой подход используется в ng2 с его жуткими "([...])" скобками в шаблонах и похожий в Aurelia с его «адаптивными байндингами»).
stardust_kid
22.10.2015 13:17+7Эта статья — отличный пример того, как не надо писать статьи. В начале 3 абзаца переливания из пустого в порожнее.
Перейдем к изучению? Нет, еще рано. React реализует модульный подход для построения приложений. Что это значит? Представьте себе конструктор. Не тот конструктор, что внутри класса, а простой детский конструктор.
Вы вообще свой текст перечитывали? Нет, перечитывали. Может перечитаете? Нет, еще рано. Постойте. Нет, читайте. А, блин, уже поздно, Евгений Ваганыч недоволен.
Потом несколько кусков кода без комментариев. Думаете, в интернете мало примеров кода на React, вся надежда на вас?
Тем читателям, кто осваивает React с нуля советую начать с официальной документации, там все понятно и подробно.kambur
22.10.2015 15:51Тут я с вами абсолютно не согласен. Для вас, как опытного программиста, статья действительно может показаться убожеством. Просто она предназначена не для вас. Я не преследовал цель создать первоклассный туториал, их и так как листьев на деревьях. А что делать человеку, который только, только делает первые шаги по пути фронт-енд разработки? Кто ему подскажет, кто направит?
Меня сильно раздражают напыщенные индюки вроде вас, которые только и умеют критиковать. Нет чтобы помочь, подсказать и сделать статью лучше. Ведь конечная цель — помощь людям!stardust_kid
22.10.2015 16:36+2Это спорно, кто из нас двоих больше подходит под определение «напыщенного индюка». В любом случае, словоблудие и малое количество информации вряд ли помогут начинающему программисту. Ваша статья не подсказывает и не направляет.
kambur
22.10.2015 16:51Поживем — увидим.
Как говорил Максим Горький:
… Мудрость жизни всегда глубже и обширнее мудрости людей.
n0ne
23.10.2015 12:46Может кто-то посоветует приличные компаненты для работы с формами? Валидация, локализация, кастомизация и т.п.
А так же кучу-кучу всяких компонентов для этих форм.
Может кому-то удалось локализировать DatePicker из Material UI?
mordaha
23.10.2015 12:57+3Обзор двух-летней давности )
На реакте сейчас пишут как-то вот так teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html
Drag13
Вам удобно писать разметку в JS коде? Можно ли ее выносить в темплейты или как бороться с этим?
kambur
Вопрос хороший. Честно говоря, я пока над этим не задумывался.
kambur
Пока пробую JSX
Drag13
JSX, как я понимаю, представляет собой псевдо разметку, которую читает IDE.
Т.е. проблема разделения вьюхи и Js остается. + Если IDE не поддерживает JSX то все совсем не удобно.
Есть методы как с этим бороться?
prog666
Можно использовать SublimeText или Atom.io
xamd
По каким-то причинам Вы считаете, что View — это не JS? React представляет собой идею функционального подхода к програмимрованию пользовательских интерфейсов, соответственно не имеет ничего общего с MVC и стараться «подбить» его к знакомой терминологии не имеет смысла.
Ну, во-первых, уже, наверное, не осталось IDE, которые не поддерживают JSX, а во-вторых Вы всегда можете писать без JSX через React.DOM.div и т.п.
Timmmm
Что бы вы не говорили, но единственная задача view это отрисовка. Нельзя мешать представление с моделью.
xamd
Конечно, React вообще не должен ничего знать о данных, которые поступают в его компоненты! Поэтому и появился flux — чтобы решить проблему работы с данными и коммуникацией между компонентами. Довольно тяжело сейчас говорить о React без упоминания Flux.
batmandarkside
Раньше как и вы мыслил. Пришло время адаптироваться. И я понял что так намного удобнее. Нет больше трёх разных сущностей как html css js. Есть одна — веб компонент. Для этого мы и держим все в одной папке, составляющие компонента. Держать разметку в js отличная идея, удобно. Как правило разметка не большая. Если у вас вёрстки в компоненте на целую страницу, значит что то пошло не так !)
Timmmm
Не буду с вами спорить, если вам удобно… Но архитектурно это не правильно, вас поставили в рамки которые противоречат здравому смыслу. Все это выглядит как костыль. Напоминает сервлеты от которых отказались, в плане верстки, уже очень давно.
batmandarkside
Яснопонятно
FaNtAsY
Мысль про веб компонент отличная — в одной папке html, css, js. Я её поддерживаю.
Но хотелось бы держать все три элемента в трёх разных файлах: *.html, *.css, *.js соответственно.
kambur
Я не уверен, что в случае с React есть необходимость разделять код от от представления.
Но тут все зависит от индивидуальных предпочтений.
Если разработчик создает много компонентов и помещает все это в один файл, получается каша. Тогда есть смысл как-то навести порядок и разделить js от html
Но я сторонник модульного подхода к разработке. Один компонент — один маленький блок html + код реализации логики.
kambur
По большому счету можно вобще отказаться от html в компонентах и писать все на чистом React.
В таком случае ваши компоненты будут универсальными.
kambur
Я думаю, что разработчикам в будущем не помешает
1. Добавить поддержку темплейтов
2. Улучшить работу со свойствами (скажем придумать объект parent через который потомок может изменять состояние родителя)
xamd
1. О каких темплейтах Вы говорите?
2. Никакого two-way data-binding. Все свойства передаются от родителя к потомку и никак иначе. А вообще, для работы с данными в React существует множество библиотек (например, redux)
DeLaVega
По второму пункту, так на секундочку — https://www.npmjs.com/package/react-binding
xamd
«Так на секундочку» — 115 скачек за последний месяц + 14 звёзд на GH и 2 issue за 5 месяцев. Я бы не полагался на подобную библиотеку при проектировании приложения, особенно если она идёт вразрез оф. идеологии.
Видимо, я не совсем ясно выразился: разумеется, сделать можно всё что угодно — это js, в конце концов. Вы так же можете использовать родной хак: facebook.github.io/react/docs/two-way-binding-helpers.html, однако даже на этой странице написано, что React спроектирован как one-way data flow система.
i360u
Шаблоны вполне можно делать в виде методов, получающих данные и возвращающих верстку, например.
xamd
Это называется «dumb components» и в React 0.14 реализуется с помощью обычных функций:
Взято из пресс-релиза 0.14
i360u
Dumb component, насколько я понимаю, это просто обертка в виде кастомного тега, содержащая верстку и не содержащая внутренней логики. Что-то типа:
Компоненты имеет смысл делать только если они переиспользуются, а внутри компонента шаблон вывода элемента можно сделать кучей разных способов.xamd
Основная идея, «пропитывающая» реакт — композиция элементов. Т.е. «шаблон вывода» в Вашем понимании составляется из маленьких dumb components, которые при аггрегации образуют то, что Вам нужно.
Разумеется, где-то вверху будет находится smart component, который будет оркестрировать весь процесс: забирать данные из какого-нибудь хранилища (будь то что-то самописное, redux store или что либо ещё) и передавать эти данные в ваши dumb components и, возможно, содержать логику а-ля «если количество записей больше нуля, показывать dumb component со списком записей, в противном случае показывать заглушку с сообщением об отсутствии записей». По факту весь реакт — это один большой шаблон, если так посмотреть :)
auine
Я поддерживаю, в реакте есть ровно все, что необходимо и следует придерживаться композитных компонентов и чистых функций
prog666
Это такая «фишка» реакта, мне вначале казалось неудобным, но быстро привыкаешь и начинаешь даже любить эту схему. А вообще можно использовать jade шаблоны. Посмотрите на react-jade.
Drag13
Любить разметку в JS? :D
prog666
Выходит что код разделен на маленькие модули всегда, и очень часто нужно менять разметку вместе с JS, в данном случае проще ничего не забыть ибо все на виду
Drag13
Возможно это дело привычки.
CJay
На мой взгляд, вычленять разметку из JSX не стоит. Почему?
1) Метод render у меня ещё ни разу не оказывался больше высоты экрана (обычно вообще строк 12-20).
2) В шаблоне логики вообще нет, никаких условных конструкций или циклов. Вся логика реализована выше в самом JS.
3) Желательно видеть, какие ноды какими ref я подписал, или какой метод вызвать при каком-то событии. Значит всё должно быть под рукой, а не переключаться между двумя файлами.
Причины за «выделение шаблона в отдельный файл» мне в голову не приходят. Станет хуже.
P.S.
По началу тоже думал, что html в JS — это какая-то дикость. Но изменил свою точку зрения. По поводу поддержки IDE — работал с jsx в Sublime без какой либо поддержки React, подсветки синтаксиса JS вполне хватало.
Drag13
Спасибо за подробный ответ.
JCHouse
Согласен, то что с первого взгляда кажется дикостью, после месяца подсаживает на именно такой стиль написания кода. Все что можно вынести в другой компонент выносится и html не выглядит грамоздко.
serf
Может быть человек давно начинал с PHP и с тех пор полюбил подход мешать код и разметку вместе. Фейсбук вот тоже с PHP дружит, вот и сделали в своем родном стиле.
arvitaly
Выносить можно, так как по сути в итоге там обычное JS-выражение. Т.е. его можно записывать в переменную, экспортировать из модуля и т.д. На практике, это обычно не нужно, так как если HTML-кода становится очень много, значит компонент плохой, а небольшое количество HTML-кода в методе render не мешает.
Так же в TypeScript запилили поддержку JSX (TSX) с проверкой типов, компиляцией и подсветкой. Пока сыровато, но обещает быть очень вкусно.
В целом, другого пути, кроме смешения JS и HTML, особо и нет, выдумывать тонны селекторов и писать отдельно обработчики событий больше нет желания, спрашивается, с чего начинали…
Igogo2012
Во перых это не JS, а JSX
И по сути react это и есть умный темплейтовый движок, так что любые файлы с расширением .jsx в проекте написанном на реакте по своей сути являются темплейтами, а уже как фреймворк отвечающий за логику можно брать до пустим backbone.
Drag13
Я могу ошибаться (тогда поправьте), я думал что JSX это псевдо-разметка, а JS это клей который обеспечивает функциональность компоненты. И имел ввиду смешение первого и второго.
Igogo2012
Из офф сайта реакта, даже разработчики утверждают что большенство людей используют его как View в MVС.
По всей своей сути react это как бы описание UI объекта.
И для того что бы не смешивать реальный JS и не тулить туда еще и разметку, разработчиками был выбран альтернативный формат .jsx который и решает данную задачу.
То есть в любом случае в Ваших jsx файлах будет как минимум не много js-подобного кода и xml-разметка (по тому что это точно не html).
Но логику и обработку данных все же лучше выносить в контроллеры и модельки Вашего приложения, дабы View оставался максимально чистым.
xamd
JSX используется в качестве препроцессора для улучшения «читабельности» кода. «Под капотом» оно транслируется в ES5-совместимый формат, т.е.
будет транслирован в
Разумеется, вы можете использовать вторую версию без каких-либо препроцессоров. Об это подробнее на оф. сайте. Так же, в версии 0.14 работа с DOM вынесена в отдельную библиотеку
react-dom
.Delphinum
Попробуйте RequireJS с плагином text.
Sunify
По факту, это не то чтобы разметка. Это язык описания дерева UI, т.е. он включает в себя, помимо тегов, вызовы компонент. А в случае с каким-нибудь React Native, там вообще нет привычных тегов.
timramone
Не нужно выносить разметку. Она связана с логикой приложения, и это не coupling, это cohesion. Компонент — это логика и представление. Тут SRP не по принципу «ты определяешь представление (как будет выглядеть какой-то элемент)» и «ты определяешь логику (как взаимодействовать с конкретным элементом)», а по принципу «ты инкапсулированный компонент, в тебе представление и логика его отображения».
Тут правда есть нюанс с тем, как верстальщика с этим работать заставить. У нас процесс построен, что верстальщик концептуально верстает элементы управления, а мы уже из этой вёрстки создаём компоненты, так что у нас нет проблемы.
На эту тему есть совершенно прекрасный доклад Александра Соловьева Как писать UI без боли. Вообще у него совершенно потрясающие видео на YouTube, очень советую и другие посмотреть :)