23Sep

logo_og
Всем привет! Сегодня у нас будет довольно важный урок, мы все ближе и ближе подбираемся к 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.

react-redux-introduction-33-638

У нас есть 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 soshace.com

Уроки React. Урок 1, Введение.

Окунемся немного в прошлое и разберемся как вообще появился React, и зачем вообще нам нужны фреймворки. Вернемся лет на 10 назад к примеру, когда был только чистый JavaScript, зачастую он выполнял простейшие задачи, такие как валидация формы и.т.п. Ключевая концепция – это абстрагироваться от неких обыденных проблем, и решать более сложные, задачи.

22. Чат через long-polling, чтение POST. Pt.1.

Всем привет! Цель этого занятия – научиться делать чат на Node.js. Для начала наш чат будет достаточно простой. Всего лишь каждый, кто заходит по этому url

http://localhost:3000/

автоматически попадает в комнату, в которой может получать сообщения от другого пользователя, находящегося в этой же самой комнате, в нашем случае переписка может вестись из разных браузеров. Мы будем делать его в начале без пользователей, базы данных, авторизации.

4 Replies to “Уроки React. Урок 7”

  1. здравствуйте автор! большое спасибо вам за труд! но есть некоторые недочеты и опечатки в кодах(различаются коды в материалах сайта и на GITe). а теперь вот в этом разборе домашнего задания ошибка при объявлении ф-ии handleDayClick. с заданными аргументами код не работает, нужно оставить один аргумент day. поправьте пожалуйста…

    1. Спасибо за комментарий! У нас все работает, вот посмотрите:
      http://joxi.ru/5md7jWaSkz4xBr
      Причина в версии Day-Picker:
      http://joxi.ru/E2pdRkNh9NB762
      C 5ой версии поменялся порядок объявления аргументов. Мы работали тогда с версией 2.5. Рекомендую поставить старую версию чтобы корректно работали остальные уроки.

  2. Денис Z 6 years ago

    Здравствуйте, Автор. Подскажите, пожалуйста, для чего вызов метода dispatch необходимо оборачивать в обертку в виде функции wrappedIncrement?

    1. Если мы передадим store dispatch, то мы его просто вызовем и передадим результат. А в props increment мы должны передать функцию, потому что хотим дать возможность Counter’у делать икремент с помощью this.props.increment()

Leave a Reply