
Всем привет! Сегодня у нас будет довольно важный урок, мы все ближе и ближе подбираемся к Redux, но для начала пробежимся по нашему домашнему заданию, оно было довольно простым, но все таки для проверки покажу как добавить наш календарь.
Установим наш модуль:
npm install react-day-picker —s
Проверим, что в нашем package.json появилась запись об этом:
"react-day-picker": "^2.3.3",
В ArticleList сделаем import самого Day-picker:
import DayPicker, { DateUtils } from "react-day-picker"
import 'react-day-picker/lib/style.css'State изменим следующим образом:
state = {
selectedArticles: null,
from: null,
to: null
}В наш div добавим к нашему select новый модуль:
<div>
<h1>Article list</h1>
{this.getRangeTitle()}
<Select
options = {options}
multi = {true}
value = {this.state.selectedArticles}
onChange = {this.handleSelectChange}
/>
<DayPicker
ref="daypicker"
selectedDays={day => DateUtils.isDayInRange(day, this.state)}
onDayClick={this.handleDayClick}
/>
<ul>
{listItems}
</ul>
</div>А ниже опишем логику работы компонента:
getRangeTitle() {
const { from, to } = this.state
const fromText = from && `Start date: ${from.toDateString()}`
const toText = to && `Finish date: ${to.toDateString()}`
return <p>{fromText} {toText}</p>
}
handleDayClick = (e, day) => {
console.log('---', day)
const range = DateUtils.addDayToRange(day, this.state);
this.setState(range)
}Теперь у нас есть функционирующий календарь и при выборе периода он отображает дату рядом с нашим заголовком “Article list”.
Обратите внимание, что наш компонент ArticleList разрастается, это довольно плохо. У нас появилось довольно много кода – это верный знак к тому чтобы вынести наши select, day-picker вместе с их логикой в отдельные компоненты, скажем в Filters. Проблема в том что если мы сделаем это прямо сейчас то у нас будет много проблем с передачей значений. Нам хотелось бы чтобы наши компоненты были атомарными, выполняющими отдельные узкопрофильные задачи. Можно сказать что наш компонент стал взрослым и нам пора задуматься о бизнес-логике, и добавить Redux или Flux.
Вспомним о том как работает React и его Virtual DOM, в картину его с трудом вписывается MVC подход и т.п., тем более в больших приложениях понять как работает тот или иной компонент достаточно сложно, так как приходиться держать очень много всего в голове (касаемо того как работает приложение), вспомните о Two-way data binding и прочих особенностях Angular и Ember.
Вместо этого команда React предложило другое решение по построении логики – Unidirectional data flow. Рассмотрим каким образом работает Redux.
У нас есть Store – они отвечают за хранение данных, именно из них View читает данные и отображает их.
Action – это events, объекты которые описывают то, что будет происходить. Они создаются для описания коммуникаций с User (клик мышкой, нажатие клавиши) или API.
Dispatcher – Actions попадают в dispatcher, он разбрасывает их по Stores, те в свою очередь реагируют, обрабатываю Actions. Например пришел action deleteArticel, store понимает, что нужно пойти и удалить какую-то статью, и оповещает View о том, что изменилось, View запрашивает все данные которые ей нужны чтобы перестроить UI.
Несколько слов об отличии Flux от Redux:
1. Во-первых Redux больше сосредоточен на концепциях функционального программирования, и все элементы Unidirectional data flow значительно сильные разделены между собой. Скажем Store это Immutubable object который отвечает исключительно за сохранение состояния.
2. В Redux отказались от внешнего Dispatcher’а который связывал все вместе. Также у нас один store на всех, а dispatcher спрятан внутри, для работы с ним разработчикам предоставлен API.
store.dispatch({
type: INCREMENT,
data: {amount}
})
store.subscribe(callback)Так как у нас один store у нас один метод подписки на него. Flux очень гибкий и имеет много store – что скорее всего будет мешать при разработке.
3. Action-creators в Redux это просто чистые функции которые возвращают вам объект, с какими либо данными, которые вместе с этим action.
function increment (amount) {
return {
type: INCREMENT,
data: {amount}
}
}
Диспетчер здесь отсутствует.
4. Stores => Reducers, это механизм перехода между состояниями. Reducers это тоже чистые функции. Это функция которая знает начальное состояние, получив какой то action должна перейти в другое состояние. Они не меняют старое, а возвращают новое:
function counter (number = o, action) {
const {type, data} = action
return type == INCREMENT ? number + data.amount : number
}Давайте прежде чем делать что-либо сложное сделаем простой Counter с Redux. Будем отображать на странице число и увеличивать его значение при нажатии на кнопку.
Зайдем в app.js и закомментируем наш Articlelist и заведем компонент Counter в папке components:
import React, { Component, PropTypes } from 'react'
class Counter extends Component {
static propTypes = {
count: PropTypes.number
};
render() {
return (
<div>
<h1>{this.props.count}</h1>
<a href="#" onClick = {this.handleIncrement}>increment</a>
</div>
)
}
handleIncrement = (ev) => {
ev.preventDefault()
console.log('---', 'incrementing')
}
}
export default CounterТакже сделаем возможность инкремента по клику. Далее вернемся в app.js и сделаем импорт компонента:
import Counter from './components/Counter'
А также добавим:
render(<Counter count = {0} />, document.getElementById('container'))Теперь установим Redux в console:
npm i redux –S
Теперь count мы будем хранить в store а не передавать его вручную.
(<Counter count = {0} />Для этого конечно же создадим Store. Создайте директорию с таким названием в src. Там создадим файл index.js. Для начала обратимся к документации Redux.
Прежде чем говорить про store, прочтите главу createStore. Все ваши данные будут жить в нем. Создается он с помощью функции:
createStore(reducer, [preloadedState], [enhancer])
с одним обязательным аргументом – reducer.
Теперь создадим наш store:
import { createStore } from 'redux'
import reducer from '../reducer'
const store = createStore(reducer, 0)
window.store = store
export default storeИ сюда же будем передавать наш reducer, который мы создадим отдельно. Создайте папку reducer в src, и соответствующий файл index.js внутри. Как мы уже говорили reducer – это обычная функция, которая принимает текущее состояние и action, и возвращает новое состояние. Так же добавим window, чтобы можно было отслеживать текущее состояний.
Пишем в нашем reducer:
export default (count, action) => {
return action.type == "INCREMENT" ? count + 1 : count
}не забудем сделать import в app.js
import store from './store'
Обратимся к документации. а именно к методам store. Нас будут интересовать 3 из них:
Задать состояние мы должны явно при создании. Обратите внимание, что мы уже это сделали, задав состояние равное 0.
Чтобы что то изменить в Store мы должны вызвать action creater с помощью второй метод dispatch. Попробуйте его в console браузера:
store.dispatch({type: ‘INCREMENT’})
Теперь состояние измениться на 1.
Теперь поработаем с отображением. В app.js добавим storeGetState, также мы хотим подписаться на изменения store и передавать их в counter, расстроим еще один способ:
render(<Counter count = {store.getState()} />, document.getElementById('container'))
store.subscribe(() => {
render(<Counter count = {store.getState()} />, document.getElementById('container'))
})Теперь при каждом обновлении store автоматически обновляется наше число.
Теперь нашу ссылку нужно подружить с action creater.
Создадим файл constants в src в котором будем хранить словарь с доступными типами actions :
export const INCREMENT = 'INCREMENT'
В reducer добавим, и кое что поправим:
import { INCREMENT } from '../constants'
export default (count, action) => {
return action.type == INCREMENT ? count + 1 : count
}Заведем директорию AC, а в ней файл counter.js:
import { INCREMENT } from '../constants'
export function increment() {
return {
type: INCREMENT
}
}В app.js сделаем import, а также dispatch и передадим его в наш counter как props:
function wrappedIncrement() {
store.dispatch(increment())
}
//render(<ArticleList articles = {articles} />, document.getElementById('container'))
render(<Counter count = {store.getState()} increment = {wrappedIncrement}/>, document.getElementById('container'))
store.subscribe(() => {
render(<Counter count = {store.getState()} increment = {wrappedIncrement} />, document.getElementById('container'))
})counter.js измениться следующим образом:
import React, { Component, PropTypes } from 'react'
class Counter extends Component {
static propTypes = {
count: PropTypes.number, increment: PropTypes.func
};
render() {
return (
<div>
<h1>{this.props.count}</h1>
<a href="#" onClick = {this.handleIncrement}>increment</a>
</div>
)
}
handleIncrement = (ev) => {
ev.preventDefault()
this.props.increment()
} } export default Counter
Вот таким способом, мы закончили простую работу с данными. Мы по клику на ссылку вызываем метод, который пришел к нам через props, этот метод вызывает store.dispatch, при этом оборачивая в него наш простой action creater, который просто создает этот объект, который описывает наш action (в counter.js – type: INCREMENT). Далее наш store, берет и вызывает свой reducer, передавая в него текущее состояние и action. Этот reducer вернет уже новое состояние, store запишет это новое состояние себе и вызовет callback который мы передали в store.subscribe. Этот callback заново вызовет метод render и перестроит заново наш компонент, и мы увидим число на один больше. Так выглядит простой Redux цикл, построенный с нуля.
Код урока находится здесь.
We are looking forward to meeting you on our website blog.soshace.com



