Вы, наверное, слышали о популярном JavaScript-фреймворке от Facebook - React. Он используется многими популярными веб-сайтами, в том числе самим Facebook и Instagram. В этой статье вы найдете 5 практических примеров на React, и которые будут вам хорошей стартовой площадкой.
React строится вокруг концепции компонентов. В отличие от таких фреймворков типа Angular и Ember которые используют двухстороннюю привязку данных для обновления HTML страницы. На мой взгляд, React более прост в освоении, чем Angular и Ember – он намного меньше и хорошо работает с jQuery и другими фреймворками. Он также очень быстрый, потому что он использует виртуальный DOM и синхронизирует только измененные части с основной страницы (доступ к DОМ по-прежнему является медленной часть современного веб-приложения). Однако, обратной стороной является то, что те вещи, которые легко могли бы быть сделаны с помощью привязки данных, занимают немного больше кода в React. В этом вы можете убедиться в приведенных ниже примерах.
Как начать использовать React
Для использования React, вам необходимо включить один файл JavaScript в вашем страницы. Facebook любезно предоставляет нам CDN, на который вы можете сослаться:
<!-- Ядро библиотеки React -->
<script src="https://fb.me/react-0.14.5.js"></script>
<!-- Библиотека ReactDOM -->
<script src="https://fb.me/react-dom-0.14.5.js"></script>
<!-- Транслятор ES6/ES7 в ES5 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.min.js"></script>
Размер файла browser.min.js превышает 1Mb, поэтому такое использование на продакшн строго не рекомендуется!
Это дает вам доступ к глобальному React-объекту, который содержит ряд полезных методов, некоторые из которых вы можете увидеть в наших примерах. Рекомендуемым способом написания реактивного веб-приложения является использование языка под названием JSX. Это немного расширенная версия JavaScript, которая позволяет инициализировать React-компоненты с помощью синтаксиса HTML непосредственно в коде. Этот код компилируется в JavaScript, перед тем как быть интерпретированным браузером. Но, достаточно говорить, давайте перейдем к примерам на React!
Таймер на React
Как я уже упоминал, приложение на React разбивается на блоки - компоненты. Они создаются путем вызова React.createClass()
с объектом опций и методов. Каждый компонент имеет состояние (объект с данными) и каждый отвечает за свой собственной рендеринг - метод render()
вызывается всякий раз, когда состояние изменяется. Вот пример создания простого таймера:
// Создание пользовательского компонента
// с помощью вызова React.createClass
var TimerExample = React.createClass({
getInitialState: function(){
// Вызывается до нашей render-функции. Объект,
// который возвращается, присваивается this.state,
// поэтому мы можем использовать его позже.
return { elapsed: 0 };
},
componentDidMount: function(){
// componentDidMount запускается по умолчанию
// после того, как компонент примонтирован к
// DOM. Мы можем установить интервал здесь:
this.timer = setInterval(this.tick, 50);
},
componentWillUnmount: function(){
// Этот метод вызывается непосредственно перед
// удалением компонента со страницы и уничтожением.
// Мы можем удалить интервал здесь:
clearInterval(this.timer);
},
tick: function(){
// Эта функция вызывается каждые 50 мс. и обновляет
// счетчик. Вызов setState заставляет компонент
// перерисовываться
this.setState({elapsed: new Date() - this.props.start});
},
render: function() {
var elapsed = Math.round(this.state.elapsed/100);
// Это даст нам число с одной цифрой после запятой (xx.x):
var seconds = (elapsed / 10).toFixed(1);
// Хотя мы возвращаем весь элемент <p>, react обновит
// только измененные части, содержащие переменную seconds.
return <p>Этот пример запущен <b>{seconds} сек.</b> назад.</p>;
}
});
ReactDOM.render(
<TimerExample start={Date.now()} />,
document.getElementById("sample")
);
Надпись с информацией будет выводиться в div с id="sample"
, так что не забудьте разместить его на вашей web-странице. Результат работы нашего примера вы можете посмотреть на странице Таймер на React.
Навигационное меню на React JS
Давайте посмотрим, как мы можем обработать событие click в React на примере создания меню навигации:
var MenuExample = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
// Обработчик click будет обновлять состояние с
// с индексом нажатого элемента меню
this.setState({focused: index});
},
render: function() {
// Здесь мы будем читать свойство items, которое было
// принято в качестве атрибута при создании компонента
var self = this;
// Метод map пройдется по массиву с пунктами меню,
// и возвратит массив с элементами <li>.
return (
<div>
<ul>{ this.props.items.map(function(m, index){
var style = '';
if(self.state.focused==index){style = 'focused';}
// Обратите внимание на использование метода bind().
// Он делает index доступным в функции clicked:
return <li className={style}
key={index}
onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Выбрано: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
// Рендер компонента меню на странице с передачей массива с пунктами меню
React.renderComponent(
<MenuExample items={['Главная', 'Услуги', 'О нас', 'Контакты']} />,
document.getElementById("sample")
);
Обратите внимание на атрибут className, которого не существует в HTML. дело в том, что JSX - это JavaScript и такие атрибуты, как class и for неодобряются. Вместо них следует использовать className и htmlFor. Результат работы нашего примера вы можете посмотреть на странице Меню на React.
Мгновенный поиск
Как вам, должно быть, известно, больше всего на свете пользователи не любят долгое ожидание. Вот как можно сеализовать на React функционал для мгновенного поиска.
// Создание компонента "мгновенный поиск"
var SearchExample = React.createClass({
getInitialState: function(){
return { searchString: '' };
},
handleChange: function(e){
// Если закомментировать эту строку, текстовое поле не изменит
// своего значения. Это потому, что в React поле не может
// измениться независимо от присвоенного ему свойства.
// В нашем случае это this.state.searchString.
this.setState({searchString:e.target.value});
},
render: function() {
var libraries = this.props.items,
searchString = this.state.searchString.trim().toLowerCase();
if(searchString.length > 0){
// Ищем и фильтрируем результаты.
libraries = libraries.filter(function(l){
return l.name.toLowerCase().match( searchString );
});
}
return <div>
<input type="text"
value={this.state.searchString}
onChange={this.handleChange}
placeholder="Введите здесь" />
<ul>
{ libraries.map(function(l,i){
return <li key={i}>{l.name} <a href={l.url}>{l.url}</a></li>
}) }
</ul>
</div>;
}
});
var libraries = [
{ name: 'Backbone.js', url: 'http://documentcloud.github.io/backbone/'},
{ name: 'AngularJS', url: 'https://angularjs.org/'},
{ name: 'jQuery', url: 'http://jquery.com/'},
{ name: 'Prototype', url: 'http://www.prototypejs.org/'},
{ name: 'React', url: 'http://facebook.github.io/react/'},
{ name: 'Ember', url: 'http://emberjs.com/'},
{ name: 'Knockout.js', url: 'http://knockoutjs.com/'},
{ name: 'Dojo', url: 'http://dojotoolkit.org/'},
{ name: 'Mootools', url: 'http://mootools.net/'},
{ name: 'Underscore', url: 'http://documentcloud.github.io/underscore/'},
{ name: 'Lodash', url: 'http://lodash.com/'},
{ name: 'Moment', url: 'http://momentjs.com/'},
{ name: 'Express', url: 'http://expressjs.com/'},
{ name: 'Koa', url: 'http://koajs.com/'},
];
// Отображаем на странице компонент SearchExample
ReactDOM.render(
<SearchExample items={ libraries } />,
document.getElementById("sample")
);
Обратите внимание, что в React обновление состояния нашего контроллера влияет на HTML код, а не наоборот. В этом примере, как только вы ввели какое-то значение текстового поля, оно будет оставаться прежним. Нажатие на клавиши не будет учитываться до тех пор, пока вы не измените текстовоое поля событием onChange (вы можете изменить такое поведение, но это рекомендованный для подобных вещей метод).
Результат работы нашего примера вы можете посмотреть на странице Мгновенный поиск на React JS.
Форма заказа на React
Настоящая мощь React JS проявляется, когда вы работаете с несколькими компонентами. Он позволяет лучше структурировать код и ввести разделение задач, что дает возможность легко использовать ваш код между различными частями приложения. Вот пример формы заказа, которая позволяет клиентам рассчитать стоимость работ за разработку сайта:
// Это уже более сложный пример, в котором мы показываем
// как компоненты могут взаимодействовать друг с другом.
var ServiceChooser = React.createClass({
getInitialState: function(){
return { total: 0 };
},
addTotal: function( price ){
this.setState( { total: this.state.total + price } );
},
render: function() {
var self = this;
var services = this.props.items.map(function(s,i){
// Создадим новый экземпляр компонента Service для
// каждого элемента массива. Обратите внимание,
// что мы передаем функцию self.addTotal в компонент.
return <Service name={s.name} key={i} price={s.price} active={s.active} addTotal={self.addTotal} />;
});
return <div>
<h1>Наши услуги</h1>
<div id="services">
{services}
<p id="total">Итого <b>${this.state.total.toFixed(2)}</b></p>
</div>
</div>;
}
});
var Service = React.createClass({
getInitialState: function(){
return { active: false };
},
clickHandler: function (){
var active = !this.state.active;
this.setState({ active: active });
// Ставим в известность ServiceChooser,
// вызывая метод addTotal
this.props.addTotal( active ? this.props.price : -this.props.price );
},
render: function(){
return <p className={this.state.active ? 'active' : ''} onClick={this.clickHandler}>
{this.props.name} <b>${this.props.price.toFixed(2)}</b>
</p>;
}
});
var services = [
{ name: 'Веб-разработка', price: 300 },
{ name: 'Дизайн', price: 400 },
{ name: 'Интеграция', price: 250 },
{ name: 'Обучение', price: 220 }
];
// Отображаем ServiceChooser и передаем
// ему массив с перечнем услуг
ReactDOM.render(
<ServiceChooser items={ services } />,
document.getElementById("sample")
);
Основная проблема, возникающая при работе с несколькими компонентами, - это как реализовать обмен данными между ними. Можно передавать необходимые данные атрибутами при их инициализации. Это работает только в случае общения компонентов «от родительского к дочернему». Для общения в другом направлении можно передать один из методов родительского компонента в дочерний с помощью атрибута.
Результат работы нашего примера вы можете посмотреть на странице Форма заказа на React JS.
Работа с jQuery и AJAX
Этот пример покажет как вы можете объединить React с jQuery и как загружать результаты при через AJAX. В то время как фреймворки типа Angular имеют свои собственные подходы для работы с AJAX, React позволяет вам использовать те библиотеки которые вы хотите.
// В этом примере у нас также два компонента - изображение и список
// изображений. Картинки получаем из Instagram через AJAX.
var Picture = React.createClass({
// Этот компонент не содержит никакого состояния, его задача -
// преобразование того, что передано атрибутами в изображение.
clickHandler: function(){
// Когда компонент кликнут, вызываем обработчик
// onClick, переданый атрибутом при создании:
this.props.onClick(this.props.id);
},
render: function(){
var cls = 'picture ' + (this.props.favorite ? 'favorite' : '');
return (
<div ref={this.props.ref} className={cls} onClick={this.clickHandler}>
<img src={this.props.src} width="200" alt={this.props.alt} />
</div>
);
}
});
var PictureList = React.createClass({
getInitialState: function(){
// Массив изображений будет передан по AJAX, а
// избранные - при целчке пользователя по изображению:
return { pictures: [], favorites: [] };
},
componentDidMount: function(){
// Когда компонент загружается,
// отправляем AJAX запрос с помощью jQuery
var self = this;
// url для загрузки популярных изображений дня из Instagram
var url = 'https://api.instagram.com/v1/media/popular?client_id=' + this.props.apiKey + '&callback=?';
$.getJSON(url, function(result){
if(!result || !result.data || !result.data.length){
return;
}
var pictures = result.data.map(function(p){
return {
id: p.id,
url: p.link,
src: p.images.low_resolution.url,
alt: p.caption ? p.caption.text : '',
favorite: false
};
});
// Обновляем состояние компонента, что вызовет render.
// Обратите внимание, обновляется только свойство
// pictures, массив с избранными картинками не удаляется.
self.setState({ pictures: pictures });
});
},
pictureClick: function(id){
// id содержит ID кликнутого изображения. Найдем
// его в массиве pictures и добавим в favorites
var favorites = this.state.favorites,
pictures = this.state.pictures;
for(var i = 0; i < pictures.length; i++){
// Находим id в массиве изображений
if(pictures[i].id == id) {
if(pictures[i].favorite){
return this.favoriteClick(id);
}
// Добавляем изображение в массив favorites
// и отмечаем, как избранное:
favorites.push(pictures[i]);
pictures[i].favorite = true;
break;
}
}
// Обновляем состояние, вызывая перерисовку
this.setState({pictures: pictures, favorites: favorites});
},
favoriteClick: function(id){
// Находим изображение в списке избранных и удалаяем
// его. После этого находим изображение в массиве
// всех изображений и снимаем метку.
var favorites = this.state.favorites,
pictures = this.state.pictures;
for(var i = 0; i < favorites.length; i++){
if(favorites[i].id == id) break;
}
// Удаляем изображение из массива favorites
favorites.splice(i, 1);
for(i = 0; i < pictures.length; i++){
if(pictures[i].id == id) {
pictures[i].favorite = false;
break;
}
}
// Обновляем состояние и перерисовываем
this.setState({pictures: pictures, favorites: favorites});
},
render: function() {
var self = this;
var pictures = this.state.pictures.map(function(p){
return <Picture id={p.id} key={p.id} src={p.src} alt={p.alt} favorite={p.favorite} onClick={self.pictureClick} />
});
if(!pictures.length){
pictures = <p>Загрузка изображений..</p>;
}
var favorites = this.state.favorites.map(function(p){
return <Picture id={p.id} key={p.id} src={p.src} alt={p.alt} favorite={true} onClick={self.favoriteClick} />
});
if(!favorites.length){
favorites = <p>Щелкните на изображение, чтобы пометить его как избранное.</p>;
}
return (
<div>
<h1>Популярные фотки из Instagram</h1>
<div className="pictures"> {pictures} </div>
<h1>Избранные изображения>/h1>
<div className="favorites"> {favorites} </div>
</div>
);
}
});
// Отрисовываем компонент PictureList и добавлем его
// в блоге div с id='sample'. В примере используется
// API ключ для тестового Instagram приложения.
// Свой собственный ключ можно сгенерить в
// http://instagram.com/developer/
ReactDOM.render(
<PictureList apiKey="642176ece1e7445e99244cec26f4de1f" />,
document.getElementById("sample")
);
Обратите внимание: в нашем примере используется один и тот же компонент Picture для отображения списка со всеми изображениям и избранными. Такое неоднократное использование кода является одним из преимуществ React.
Результат работы нашего примера вы можете посмотреть на странице Работа с jQuery и AJAX вReact JS.
Что дальше?
Вы уже достаточно далеко продвинулись в изучении React и теперь можете приступать к созданию более сложных приложений. Для более лучшего усваивания материала, вы можете ознакомиться с уроками Интернет магазин на React.js и Flux и Изучаем React и Google maps.