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.com

 

3. Уроки Express.js. Шаблонизация с EJS: Layout, Block, Partials

В реальной жизни у нас обычно больше, чем один шаблон. Более того, если уж так получилось, что мы делаем сайт со страницами, то, как правило, бывает так, что у нас множество страниц есть в одинаковом оформлении. Шаблонная система должна это предусматривать. К сожалению, ejs не очень хорошо с этим справляется. Поэтому, мы сейчас поставим немного другую систему для шаблонизации , которая называется ejs-locals(добавим в app.js)

21. Уроки Node.js. Writable Поток Ответа res, Метод pipe. Pt.1

Нашим следующим шагом будет использование потоков для работы с сетевыми соединениями. И начнем мы с отдачи посетителю файлов. Если помните, у нас была такая задача: если посетитель запросит следующий url, то отдать ему файл. Создадим файл pipe.js со следующим кодом:

18. Уроки Node.js. Работа с Файлами, Модуль fs

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

2 Replies to “Уроки React . Урок 8”

  1. Денис Z 6 years ago

    Здравствуйте, Автор. В данном уроке столкнулся с проблемой, диспатча articles. Самое интересное, что в reducer/article.js я вывожу отфильтрованный массив статей, когда приходит экшн DELETE_ARTICLE, все по вашему примеру, но store почему то не перестраивает потом компонент. Вы не могли бы помочь разобраться? привожу ссылку на свой проект на Гитхабе: https://github.com/denis862008/react-training

  2. Добрый день! К сожалению сейчас совсем нет времени посмотреть Ваш код. Посмотрите код следующего урока там все довольна просто. Еще обратите внимание на версии библиотек и.т.п. которые мы использовали тогда для этих статей, лучше придерживаться их для корректной работы.

Leave a Reply