Добавить новость
ОБНОВЛЕНИЕ 2018: в учебнике хорошая теория, но ему уже два года. Проверяйте версии пакетов. За выходом нового учебника можно следить в telegram канале или twitter
На канале так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы, присоединяйтесь!
Добавить новость
Что такое добавление новости?
Это форма, в которую мы вводим необходимые данные.
Это "лента новостей", которая отображает наши данные.
После создания новости должно генерироваться событие, на которое должна быть подписана новостная лента. Иначе, как лента узнает о том, что нужно показать новую новость?
Очевидно, что нам нужна какая-то "система событий", чтобы научить компонент <Add />
генерировать событие, а компонент <News />
отображать. Так как компонент <News />
имеет статичный атрибут data, было бы логично предположить, что в data нужно "скидывать" динамически сформированную переменную. Конечно же, это место где нужно использовать state.
Подытожим:
компонент
<App />
должен иметь переменную в state - "новости", которую передавать в компонент<News />
.Компонент
<App />
должен уметь слушать событие "добавлена новость".Компонент
<App />
так же должен уметь отписываться от прослушивания события.
var App = React.createClass({
getInitialState: function() {
return {
news: my_news
};
},
componentDidMount: function() {
/* Слушай событие "Создана новость"
если событие произошло, обнови this.state.news
*/
},
componentWillUnmount: function() {
/* Больше не слушай событие "Создана новость" */
},
render: function() {
console.log('render');
return (
<div className='app'>
<Add />
<h3>Новости</h3>
<News data={this.state.news} />
</div>
);
}
});
Мы добавили переменную в state (с начальным состоянием новостей - массивом my_news), ее же стали передавать в качестве свойств, для компонента <News />
. А также, "как будто подписались" на прослушивание события в момент примонтирования компонента, и "как будто отписались" в момент "перед удалением компонента". В нашем примере, компонент <App />
не может быть удален, но тем не менее "не забывать отписываться" - хорошая практика.
На втором берегу, у нас компонент <Add />
, который должен уметь генерировать событие в обработчике onBtnClickHandler.
Кстати, переименуйте кнопку "показать alert" -> "Добавить новость", так как мы уже почти готовы!
Глобальная система событий
Напомню, у нас возник вопрос о необходимости взаимодействия двух компонентов. Эти компоненты не состоят в отношении родитель-потомок.
Предлагаю воспользоваться решением EventEmitter, для этого скачайте/добавьте библиотеку (.min версия) в index.html, перед app.js.
index.html
<!DOCTYPE html>
<html>
<head>
<title>React [RU] Tutorial</title>
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="root"></div>
<script src="js/react/react.js"></script>
<script src="js/react/react-dom.js"></script>
<script src="js/react/browser.min.js"></script>
<script src="js/EventEmitter.js"></script>
<script type="text/babel" src="js/app.js"></script>
</body>
</html>
Теперь мы можем в app.js добавить глобальную переменную:
...
window.ee = new EventEmitter();
...
Благодаря которой, можем генерировать событие в обработчике onBtnClickHandler компонента <Add />
...
onBtnClickHandler: function(e) {
e.preventDefault();
var author = ReactDOM.findDOMNode(this.refs.author).value;
var text = ReactDOM.findDOMNode(this.refs.text).value;
var item = [{
author: author,
text: text,
bigText: '...'
}];
window.ee.emit('News.add', item);
},
...
window.ee.emit('News.add', item);
= сгенерируй событие 'News.add' и передай в качестве данных - item.
И наконец, благодаря window.ee мы можем подписываться/отписываться в <App />
:
...
componentDidMount: function() {
var self = this;
window.ee.addListener('News.add', function(item) {
var nextNews = item.concat(self.state.news);
self.setState({news: nextNews});
});
},
componentWillUnmount: function() {
window.ee.removeListener('News.add');
},
...
window.ee.addListener
- принимает в качестве аргументов имя события и функцию-обработчик. Чтобы внутри функции-обработчика (callback) использовать this - мы сохранили его чуть выше в переменную self.
Интересный момент: var nextNews = item.concat(self.state.news);
Мы создали новый массив, в котором первым элементом поставили новую новость, чтобы она была верхней в списке.
Кстати, было бы неплохо частично очищать форму после добавления новости: а именно, удалять текст новости, но оставлять "чекбокс" и автора. Внесите эти изменения в onBtnClickHandler:
...
onBtnClickHandler: function(e) {
e.preventDefault();
var textEl = ReactDOM.findDOMNode(this.refs.text);
var author = ReactDOM.findDOMNode(this.refs.author).value;
var text = textEl.value;
var item = [{
author: author,
text: text,
bigText: '...'
}];
window.ee.emit('News.add', item);
textEl.value = '';
this.setState({textIsEmpty: true});
},
...
Удобно, что кнопка "дизейблится" (disable) после очистки.

Кхм, итоговый сценарий:
js/app.js
'use strict';
var my_news = [
{
author: 'Саша Печкин',
text: 'В четчерг, четвертого числа...',
bigText: 'в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили чёрными чернилами чертёж.'
},
{
author: 'Просто Вася',
text: 'Считаю, что $ должен стоить 35 рублей!',
bigText: 'А евро 42!'
},
{
author: 'Гость',
text: 'Бесплатно. Скачать. Лучший сайт - http://localhost:3000',
bigText: 'На самом деле платно, просто нужно прочитать очень длинное лицензионное соглашение'
}
];
window.ee = new EventEmitter();
var Article = React.createClass({
propTypes: {
data: React.PropTypes.shape({
author: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired,
bigText: React.PropTypes.string.isRequired
})
},
getInitialState: function() {
return {
visible: false
};
},
readmoreClick: function(e) {
e.preventDefault();
this.setState({visible: true});
},
render: function() {
var author = this.props.data.author,
text = this.props.data.text,
bigText = this.props.data.bigText,
visible = this.state.visible;
return (
<div className='article'>
<p className='news__author'>{author}:</p>
<p className='news__text'>{text}</p>
<a href="#"
onClick={this.readmoreClick}
className={'news__readmore ' + (visible ? 'none': '')}>
Подробнее
</a>
<p className={'news__big-text ' + (visible ? '': 'none')}>{bigText}</p>
</div>
)
}
});
var News = React.createClass({
propTypes: {
data: React.PropTypes.array.isRequired
},
getInitialState: function() {
return {
counter: 0
}
},
render: function() {
var data = this.props.data;
var newsTemplate;
if (data.length > 0) {
newsTemplate = data.map(function(item, index) {
return (
<div key={index}>
<Article data={item} />
</div>
)
})
} else {
newsTemplate = <p>К сожалению новостей нет</p>
}
return (
<div className='news'>
{newsTemplate}
<strong
className={'news__count ' + (data.length > 0 ? '':'none') }>Всего новостей: {data.length}</strong>
</div>
);
}
});
var Add = React.createClass({
getInitialState: function() {
return {
agreeNotChecked: true,
authorIsEmpty: true,
textIsEmpty: true
};
},
componentDidMount: function() {
ReactDOM.findDOMNode(this.refs.author).focus();
},
onBtnClickHandler: function(e) {
e.preventDefault();
var textEl = ReactDOM.findDOMNode(this.refs.text);
var author = ReactDOM.findDOMNode(this.refs.author).value;
var text = textEl.value;
var item = [{
author: author,
text: text,
bigText: '...'
}];
window.ee.emit('News.add', item);
textEl.value = '';
this.setState({textIsEmpty: true});
},
onCheckRuleClick: function(e) {
this.setState({agreeNotChecked: !this.state.agreeNotChecked});
},
onFieldChange: function(fieldName, e) {
if (e.target.value.trim().length > 0) {
this.setState({[''+fieldName]:false})
} else {
this.setState({[''+fieldName]:true})
}
},
render: function() {
var agreeNotChecked = this.state.agreeNotChecked,
authorIsEmpty = this.state.authorIsEmpty,
textIsEmpty = this.state.textIsEmpty;
return (
<form className='add cf'>
<input
type='text'
className='add__author'
onChange={this.onFieldChange.bind(this, 'authorIsEmpty')}
placeholder='Ваше имя'
ref='author'
/>
<textarea
className='add__text'
onChange={this.onFieldChange.bind(this, 'textIsEmpty')}
placeholder='Текст новости'
ref='text'
></textarea>
<label className='add__checkrule'>
<input type='checkbox' ref='checkrule' onChange={this.onCheckRuleClick}/>Я согласен с правилами
</label>
<button
className='add__btn'
onClick={this.onBtnClickHandler}
ref='alert_button'
disabled={agreeNotChecked || authorIsEmpty || textIsEmpty}
>
Опубликовать новость
</button>
</form>
);
}
});
var App = React.createClass({
getInitialState: function() {
return {
news: my_news
};
},
componentDidMount: function() {
var self = this;
window.ee.addListener('News.add', function(item) {
var nextNews = item.concat(self.state.news);
self.setState({news: nextNews});
});
},
componentWillUnmount: function() {
window.ee.removeListener('News.add');
},
render: function() {
console.log('render');
return (
<div className='app'>
<Add />
<h3>Новости</h3>
<News data={this.state.news} />
</div>
);
}
});
ReactDOM.render(
<App />,
document.getElementById('root')
);
Ужасно длинный файл. Так хочется разбить его на кусочки, использовать ES6 синтаксис, модули, сжимать его в конце-концов...
Не торопите события! На данный момент было важно показать вам именно работу с реактом, как с "просто еще одной библиотекой". Организация кода "на современный лад" - входит в мои планы. Быть может, когда вы будете читать этот текст, соответствующая глава уже будет написана.
Полезная ссылка по вопросу организации взаимодействия между компонентами http://stackoverflow.com/questions/21285923/reactjs-two-components-communicating/31563614#31563614
Итого: Мы научили компоненты совместной работе. Посмотрели на реализацию EventEmitter для браузера.
Данный урок хорош тем, что он показывает подход к реализации глобальной системы событий в React.js
Мне очень симпатичен Redux, который элегантно решает эту проблему. По нему тоже есть подробный туториал на русском. Рекомендую к изучению.
Исходный код на данный момент.
Last updated
Was this helpful?