Всем привет! Сегодня начнем наш урок с домашнего задания, сделав его вместе. Главной целью этих уроков является помочь читателю научиться думать категориями данных – это является основой функционального программирования как такового. когда вы любую систему, например UI, вы описываете набором данных. Пока в наших уроках мы делали все самыми простыми способами. У нас были статьи которые содержат в себе все необходимое, счетчик который содержит в себе число и т.д. Мы пока не делали ничего сложнее, не объединяли все эти элементы, мы не думали какие данные стоит хранить в store а какие в state компонентов. Поэтому давайте все это обсудим. Во-первых, какие данные и где следует держать?
В идеале в store должна храниться вся информация которая достаточна для описания вашего приложения , т.е. у вас в state компонентах ничего не должно быть, но на практике это не совсем так, и нет смысла переносить некоторые элементы из state в store. Например бывают проекты где при работе с формами при каждом нажатии изменения заносятся в state, с нашей точки зрения это лишнее. При работе с довольно стандартным приложением вы должны хранить в store такие данные которых будет достаточно для полного восстановления вашего приложения. Это дает понимание чем можно пожертвовать при написании приложения. Например если пользователь перезагрузит страницу и у него закроется календарь – что наверное не так важно, это можно отнести в state. Здесь лучше хранить то,что не жать потерять. Например, нам важно количество статей, т.е. если удалились пара статей вам важно об этом знать. Вы хотели бы информацию эту информацию – ее место в store. Как раз мы подошли к вопросу о фильтрах и их создании. Давайте сделаем reducer который будет хранить значение фильтров. Сейчас мы все храним в компоненте в ArticleList , а мы их вынесем в отдельный компонент/контейнер.
Создадим Filters.js в папке containers. И сюда мы вынесем все что нам необходимо для фильтров. Таким образов в ArticleList останется:
import React, { Component } from 'react' import Article from './Article/index' import oneOpen from '../decorators/oneOpen' import Filters from '../containers/Filters' class ArticleList extends Component { render() { const { articles, isItemOpen, toggleOpenItem } = this.props const listItems = articles.map((article) => <li key={article.id}> <Article article = {article} isOpen = {isItemOpen(article.id)} openArticle = {toggleOpenItem(article.id)} /> </li>) return ( <div> <h1>Article list</h1> <Filters /> <ul> {listItems} </ul> </div> ) } } export default oneOpen(ArticleList)
Где мы добавим import Filters и отобразим их в return.
Все остальное у нас переместилось в компонент Filters.
Добавляем { conncect } в import и в export default
import React, { Component, PropTypes } from 'react' import Select from 'react-select' import 'react-select/dist/react-select.css' import DayPicker, { DateUtils } from "react-day-picker" import 'react-day-picker/lib/style.css' import { connect } from 'react-redux' class Filters extends Component { static propTypes = { }; state = { selectedArticles: null, from: null, to: null } render() { const { articles } = this.props const options = articles.map((article) => ({ label: article.title, value: article.id })) return ( <div> {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} /> </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) => { const range = DateUtils.addDayToRange(day, this.state); this.setState(range) } handleSelectChange = (selectedArticles) => { this.setState({ selectedArticles: selectedArticles.map(o => o.value) }) } } export default connect(state => { const { articles } = state return { articles } })(Filters)
Проверяем, все должно работать, хотя ничего особенного мы не сделали пока. Что действительно важно так это то что нам не нужен state. Мы должны сделать так чтобы все эти элементы такие как selectedArticles, from хранились в reducer . Создадим в папке reducer файл filters.js :
import { } from '../constants' const defaultFilters = { selectedArticles: null, from: null, to: null } export default (filters = defaultFilters, action) => { const { type, payload, response, error } = action switch (type) { } return filters }
В reducer/index.js подключим :
import articles from './articles' import counter from './counter' import filters from './filters' import { combineReducers } from 'redux' export default combineReducers({ count: counter, articles, filters })
И сразу же научим их меняться, для этого зайдем в constants.js и добавим:
export const CHANGE_FILTERS = 'CHANGE_FILTERS'
Также не забудем создать action creater – filters.js:
import { CHANGE_FILTERS } from '../constants' export function changeFilters(change) { return { type: CHANGE_FILTERS, payload: { change } } }
Теперь доделаем наш reducer таким образом:
import { CHANGE_FILTERS } from '../constants' const defaultFilters = { selectedArticles: [], from: null, to: null } export default (filters = defaultFilters, action) => { const { type, payload, response, error } = action switch (type) { case CHANGE_FILTERS: return Object.assign({}, filters, payload.change) } return filters }
В return внизу чтобы состояние не мутировали добавим следующую запись. Напомню, что в React мы не меняем прошлое состояние а всегда возвращаем новое. Если Redux видит что ничего не поменялось он просто не вызовет callbacks которые подписаны на ваши изменения.Теперь в containers/filters.js делаем импорт нашего AC:
import { changeFilters } from '../AC/filters'
Также добавим добавим его в connect, а также из state будем брать не только статьи но и саами фильтры:
export default connect(state => { const { articles, filters } = state return { articles, filters } }, { changeFilters })(Filters)
Теперь все, что мы читали из state, нам нужно читать из filters:
render() { const { articles, filters } = this.props const options = articles.map((article) => ({ label: article.title, value: article.id })) return ( <div> {this.getRangeTitle()} <Select options = {options} multi = {true} value = {filters.selectedArticles} onChange = {this.handleSelectChange} /> <DayPicker ref="daypicker" selectedDays={day => DateUtils.isDayInRange(day, filters)} onDayClick={this.handleDayClick} /> </div> ) } getRangeTitle() { const { from, to } = this.props.filters const fromText = from && `Start date: ${from.toDateString()}` const toText = to && `Finish date: ${to.toDateString()}` return <p>{fromText} {toText}</p> } handleDayClick = (e, day) => { const { filters, changeFilters } = this.props const range = DateUtils.addDayToRange(day, filters); changeFilters(range) } handleSelectChange = (selectedArticles) => { this.props.changeFilters({ selectedArticles: selectedArticles.map(o => o.value) }) } }
Теперь все данные ходят через Redux store и полностью хранятся там.
Поговорим немного о фильтрах. Теперь в Articles.js нам доступны filters. Сейчас в store хранятся как статьи так и фильтры. Мы хотим видеть уже отфильтрованные статьи. Лучшее место где это можно делать так это в connect. Саму функцию фильтрации можно выносить в utils к примеру, можно писать прямо в Articles, это уже на ваш вкус. Напишем пока здесь, хотя в реальной жизни лучше вынести это в отдельный файл:
export default connect(({ articles, filters }) => { return { articles: filterArticles(articles, filters) } })(Articles) function filterArticles(articles, { from, to, selectedArticles }) { return articles .filter((article) => selectedArticles.length ? selectedArticles.includes(article.id) : true) .filter(article => (!from || Date.parse(article.date) > from) && (!to || Date.parse(article.date) < to)) }
С самого начала буде хранить пустой массив в reducer/filters.js:
const defaultFilters = { selectedArticles: [], from: null, to: null
Таким образом мы отдельно храним данные про статьи, отдельно информацию про фильтры. Когда нам нужно мы берем информацию из обеих источников, из фильтров и статей, фильтруем . А в Articles у нас благодаря connect появляются наши данные, готовые, отфильтрованные. Данные в store попадают только через reducer’ы. В store храниться какое-то состояние, когда происходит dispatch какого-то action с помощью storeDispatch, с помощью action creator который обернут у нас при помощи connect. Когда мы делаем dispatch action, он попадает в reducers, они берут старое состояние store и action который мы dispatch (например изменение фильтра), мы берем старые фильтры которые у нас были и action, из него достаем change и делаем merge старых фильтров и новых. Только все это делаем в новый объект, чтобы у нас не мутировали старые. После того как reducer закончил свою работу store берет новое состояние, сохраняет его и вызывает все callbacks которые были подписаны с помощью store.subscribe и обновляет соответствующие компоненты.
Код домашнего задания находиться здесь.
We are looking forward to meeting you on our website soshace.com