07Oct


Всем привет! Сегодня начнем наш урок с домашнего задания, сделав его вместе. Главной целью этих уроков является  помочь читателю научиться думать категориями данных – это является основой функционального программирования как такового. когда вы любую систему, например 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 и обновляет соответствующие компоненты.

Код домашнего задания находиться здесь.

react-js

We are looking forward to meeting you on our website soshace.com

Уроки React. Урок 2, Домашнее задание.

Давайте пробежимся по нашему домашнему заданию. Смысл использования React это создание компонентов из вашего функционала, т.е. дробление приложение на маленькие независимые части. В нашем случае комментарии нужно сделать комментарии отдельным компонентами. Также хорошей практикой считается иметь как можно больше stateless компонентов. К примеру в нашем случае это файл comment.js.

Уроки React. Урок 12.

Всем привет! Сейчас мы пробежимся по нашему домашнему заданию, а также дадим важные комментарии , которые помогут Вам в дальнейшей работе с React. Для удобства Вам понабиться видеть перед собой код, поэтому перейдите на нужный commit и мы продолжим. Начнем с connect.connect нужен нам, чтобы обратиться к store. Добавим небольшую ремарку, что если Вы можете передать данные используя обычные props – передавайте. Например в containers/Articles.js мы обращаемся к store чтобы из его state достать необходимые нам статьи:

Уроки React. Урок 4. Домашнее Задание.

Поговорим о нашем домашнем задании. Стоит отметить что при разработке decorators/mixins вся логика в большинстве случаев работает прекрасно. Она была реализована нами в классе, для выполнения домашнего задания оставалось вынести ее в decorator и соответствующий mixin. Так будет выглядеть наш decorator:

Leave a Reply