Изучение React — для чего, откуда, как?
С чего начать изучение новой библиотеки или фрейморка? Сразу же найти статью на Хабре и с головой погрузиться в пучину практических примеров? Или сначала тщательно изучить официальную и неофициальную документацию, прежде чем перейти к практике? Именно между этими вопросами будет метаться ваше сознание, когда вы решите узнать, что же такое ReactJS. Чтобы желание учиться не умерло, как знаменитый ослик, обязательно загляните под капот.
Готовь сани летом а телегу зимой
Мало кто на перед задумывается о том, как готовое приложение будет работать на боевом сервере. Обычно эти вопросы решаются в самом конце, когда код уже написан, и пути назад нет. Именно поэтому прежде чем заняться изучением самой библиотеки, определитесь с вопросами компиляции кода вашего будушего творения. Варианта тут два. Для учебы, или скажем, демоверсии сайта, подойдет вариант компиляции на стороне клиента. Ничего делать не надо, все за вас сделает браузер, “на лету” так сказать. Но вот для готового продукта, я бы посоветовал настроить компиляцию на стороне сервера. Благо инструментов для этого предостаточно. Тут вам и 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">
<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'));
Как вы уже поняли, 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' };
},
clearError: function() {
this.setState({errorCode: 0, errorMessage: ''});
},
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} />
</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(){
return { errorCode: 0, errorMessage: '', loginActions: [], defaultLogin: 'admin', defaultPassword: 'password' };
},
clearActionList: function() {
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>
<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 потраченного на него времени — каждый решает для себя. Пройдено только пол пути. Для кого-то эта дорога была легкой, для кого-то — не очень. Кто-то пойдет дальше, а кто-то остановиться. И я очень надеюсь, что моя статья будет хорошим подспорьем новичкам.