30Sep

Без названия

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

Первое что хотелось бы сделать, это избавиться от  всех этих подписок, оборачиваний, как например в app.js. Для этого существует библиотека react-redux, установим ее:

npm i react-redux –S

Сам по себе Redux можно использовать где угодно, а наша библиотека помогает подружить React с Redux и писать меньше кода.

В app.js добавим

import { Provider} from 'react-redux'

и

render(
<Provider>
<Counter count = {store.getState()} increment = {wrappedIncrement}/>, 

</Provider>,document.getElementById('container'))

Но обычно это все выносят в отдельный контейнер, создадим папку containers, и создадим в ней файл Root.js. В нем напишем компонент который берет этот Provider и оборачивает в него ваше приложение , собственно Counter.js.

import React, { Component, PropTypes } from 'react'
import { Provider } from 'react-redux'
import Counter from './Counter'
import store from '../store'

class RootContainer extends Component {
    static propTypes = {

    };

    render() {
        return (
            <Provider store = {store}>
                <Counter />
            </Provider>
        )
    }
}

Давайте перенесем его в нашу директорию тоже.

В app.js удалим все лишнее оставив следующий код, также добавим Root, и метод render для него:

import React from 'react'
import { render } from 'react-dom'
import Root from './containers/Root'

render(<Root />, document.getElementById('container'))

Этот root container заворачивает все наше предложение и позволяет дальше использовать Redux. В counter мы хотим получить данные, которые хранятся в store (‘count’) и возможность dispatch’ить наши action в этот store (‘increment’). Для этого в react-redux есть connect – это decorator. Добавим его в counter.js:

import { connect } from 'react-redux'

В export default напишем:

export default connect((state) => {
    return {count: state}
}, {
    increment
})(Counter)

Connect принимает в себя 4 параметра, но нужно знать о двух из них. Первый – это функция которая принимает state вашего store и достает из него то, что Вам нужно, сейчас это весь state. Т.е. мы возвращаем объект который merge к нашим props. В итоге мы получим count – как состояние нашего store. Вторым аргументом он принимает объект, в который мы можем передать обычные action creators, и он уже произведет с ними действия такие как: wrapper, dispatch и т.п.

Добавим еще один import:

 import { increment } from '../AC/counter'

Проверим, все должно работать. Мы написали много кода и большинство этого кода уже переиспользуется. Connect –  довольно умный и умеет намного больше чем просто подписаться на store. Он обновляет обновляет ваш компонент, который меняется, также он добавляет проверку shallow ваших props. Т.е. если у вас не поменялся результат в export, была цифра 1 и осталась 1, то перестраивать компонент он не будет, сделав проверку за Вас.

Следующее что мы сделаем это наконец добавим нормальный store, в котором будет больше данных. Для этого все данные в store мы будем хранить в виде объектов.

Во первых в store (index.js) начальное состояние будет не 0, а объект в котором мы будем хранить все, в том числе наши статьи:

const store = createStore(reducer, {})

Также у нас усложнится reducer. Чтобы не писать большую функцию сделаем несколько reducers в соответствующей папке. Они будут отвечать за count, articles.

articles.js :

export default (articles = [], action) => {
    return articles
}

counter.js :

import { INCREMENT } from '../constants'

export default (count = 0, action) => {
    return action.type == INCREMENT ? count + 1 : count
}

Теперь осталось все свести в одну большую функцию, в index.js напишем:

import articles from './articles'
import counter from './counter'
import { combineReducers } from 'redux'

export default combineReducers({
    count: counter,
    articles
})

combineReducers – принимает объект, в котором мы описываем ключ – как будет выглядеть элемент в нашем store, будь то articles или count, и какой reducer будет за него отвечать. Ключ – это название поддерева в store, а значение – reducer.

Теперь в containers/Counter.js export будет выглядеть так:

export default connect((state) => {
    const { count } = state
    return { count }
}, {
    increment
})(Counter)

Проверяем – работает! Вот таким образом мы можем строить достаточно большие store в которых будет храниться много данных, но они будут независимы друг от друга.

В Redux есть разделение на контейнеры и компоненты. «Умные» компоненты, связанные со store такие как Counter. И компоненты, которые просто получают данные и делают их render. У нас сейчас будет два контейнера – Counter и ArticleList.

А теперь добавим в папку containers – Articles.js. Он будет читать статьи из store. Поэтому мы в reducer/articles.js будем брать не просто массив:

import { articles as defaultArticles } from '../fixtures'

export default (articles = defaultArticles, action) => {

    return articles
}

В containers/Article.js:

import React, { Component, PropTypes } from 'react'
import ArticleList from '../components/ArticleList'
import { connect } from 'react-redux'
import { deleteArticle } from ‘../AC/articles’

class Articles extends Component {
    static propTypes = {

    };

    render() {
      const { articles, deleteArticles } = this.props
      return <ArticleList articles = {articles} deleteArticle = {deleteArticle} />
    }
}

export default connect(
    ({articles}) => ({articles}),
    { deleteArticle }
)(Articles)

Также добавим возможность удаления статей. Результаты передаём в connect. В AC создадим articles.js и опишем action creators которые отвечают за статьи:

import { DELETE_ARTICLE } from '../constants'

export function deleteArticle(id) {
    return {
        type: DELETE_ARTICLE,
        payload: { id }
    }
}

Также не забудем завести эту константу, в constants.js :

export const DELETE_ARTICLE = 'DELETE_ARTICLE'

Обратимся к нашему reducer – articles.js. Он умеет инициализироваться, но пока никак  не обрабатывает никакие actions. Изменим его следующим образом:

import { articles as defaultArticles } from '../fixtures'
import { DELETE_ARTICLE } from '../constants'

export default (articles = defaultArticles, action) => {
    const { type, payload } = action

    switch (type) {
        case DELETE_ARTICLE:
            return articles.filter(article => article.id != payload.id)
    }
    return articles

Reducers должны возвращать новое состояние не меняя старое. Таким образом мы не можем изменить наш массив. В нашем случае мы можем вернуть отфильтрованный массив с помощью методом filter. Все здесь опирается концепцию immutable данных, которая лежит в основе функционального программирования. Таким образом мы вернем новый список статей, исключая ту которую хотим удалить.

Теперь добавим в containers/Root.js следующий код:|

import Articles from './Articles'

Здесь же в return кое-что изменим. Есть ограничение что в provider мы можем передать только одно ограничение, поэтому их надо завернуть в какой-нибудь <div>. Обычно у вас существует один корневой компонент, но тем не менее, при передаче двух умных компонентов в provider на первый уровень нужно их обернуть:

return (
            <Provider store = {store}>
                <div>
                    <Counter />
                    <Articles />
                </div>
            </Provider>
        ) 

Проверим. Мы научились отображать наши статьи, берем их прямо из store, также у нас есть метод чтобы их удалить, пока мы его не используем. Connect мы будем использовать пока только для того чтобы достать данные. В containers/Article.js изменим код следующим образом:

import React, { Component, PropTypes } from 'react'
import ArticleList from '../components/ArticleList'
import { connect } from 'react-redux'

class Articles extends Component {
    static propTypes = {

    };

    render() {
        const { articles } = this.props
        return <ArticleList articles = {articles} />
    }
}

export default connect(
    ({articles}) => ({articles})
)(Articles)

Теперь перейдем в Article/index.js добавим:

import { deleteArticle } from '../../AC/articles'

import { connect } from 'react-redux'

И завернем нашу статью в connect:

export default connect(null, { deleteArticle })(Article)

Еще добавим кнопку delete:

return (
                <div className="article">
                    <h1 onClick = {openArticle}>{ title } <a href="#" onClick = {this.handleDelete}>delete me</a></h1>
                    <CSSTransitionGroup transitionName="article" transitionEnterTimeout={500} transitionLeaveTimeout = {300}>
                        {body}
                    </CSSTransitionGroup>
                </div>
            )
    }

    handleDelete = (ev) => {
        ev.preventDefault()
        const { article, deleteArticle } = this.props
        deleteArticle(article.id)
    }
}

export default connect(null, { deleteArticle })(Article)

Теперь проверим – все отлично работает!

Домашнее задание:

Сделать reducer и часть store для фильтров и сделать функционал фильтрации статей. Т.е. чтобы при выборе отрезка времени в календаре отображались только те статьи, которые были добавлены в этих числах. И вынести все эти фильтры из ArticleList в компонент filters. Через Redux пропускаете эти фильтры, таким образом в ArticleList будете отображать отфильтрованные данные.

Код урока можно найти тут.

react_header

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

 

22. Чат Через Long-Polling. Чтение POST. Pt.2.

Соответственно, что бы мы не записали, отправляется одно и то же. Давайте мы это поправим. Сообщения у нас отправляются методом POST. Для того, чтобы считать этот метод из req, нужно поработать с ним, как с потоком. Для этого посмотрим на следующую схему, которая описывает жизненный цикл запроса, а именно объектов req и res.

10. Уроки Node.js. Node.JS как веб-сервер

Всем привет! На этом занятии мы познакомимся с Node.js уже в роли веб-сервера. Создадим для этого новое приложение. Настройте пожалуйста свой редактор таким образом чтобы он знал что вы делаете именно Node.js-проект и поддерживал соответствующие автодополнения и глобальные переменные. Далее создадим server.js и в нем первым делом подключаю модуль:

var http = require(‘http’);

Leave a Reply