11Nov

react_14

Всем привет! Продолжаем работу над нашим приложением. Мы с Вами уже умеем делать простые обращения к серверу и в 80% случаев этих знаний будет достаточно для выполнения типовых задач. Но конечно же, как работать с более сложными вещами мы тоже разберемся. Но прежде чем начать с этим разбираться спешу Вас предупредить, что middleware  далеко не всегда надо писать самостоятельно.  Т.к. это такие часто используемые  (переиспользуемые) элементы, которые  уже были написаны до Вас. Рекомендуем ознакомиться с данным ресурсом, здесь можно найти огромное количество разнообразных  middlewares и т.д.  К примеру вот готовый logger. Мы писали с Вами logger чтобы попрактиковаться, но вот пример прекрасного готового решения.

Иногда бывает так, что мы хотим выполнить dispatch нескольких actions. Например в AC/articles.js deleteArticle мы делаем dispatch одного action. Но иногда нам нужно проделать это над несколькими actions . Чтобы каждый раз не писать для этого middleware  мы можем просто вернуть массив type:'OTHER_ACTION" 

export function deleteArticle(id) {
    return [{
        type: DELETE_ARTICLE,
        payload: { id }
    }, {
        type:'OTHER ACTION'
    }]

}

и чтобы они были  dispatch (‘задиспачились’) один за другим. Для этого существует middleware redux-multi. Как она работает?
Вспомните как мы с вами делали проверку – интересует ли нас какой-то action или нет? Мы делали проверку по какому-либо флажку (как в middlewares/api.js), но можно это сделать и по-другому. Например, мы знаем, что в reducers должны попасть “плоские” объекты. Поэтому мы можем сделать проверку по типу. Если тип не объект, а массив – то разбить их и сделать dispatch одного за другим.  Это и делает redux-multi. Если к нему приходит массив с actionsон делает dispatch одного за другим.  Нужно понимать, что разбивка на action происходит внутри  middleware, но вы пишите их в одном месте для удобства (в action creator). И пожалуй самая популярная middleware в Redux это redux-thunk. По аналогии redux-multi он проверяет по типу что к нему приходит, и если это не объект а функция, то он вызывает ее внутри себя и далее уже происходит dispatch.  С его помощью можно делать асинхронные actions(на самом деле как бы асинхронные). Например загрузку статей мы смогли бы описать несколько по-другому.

Установим redux-thunk:

npm i redux-thunk --S

Зайдем в AC/articles.js и добавим:

export function loadAllArticlesAlt() {
    return (dispatch, state) => {
        dispatch({
            type: LOAD_ALL_ARTICLES + START
        })

        setTimeout(() => {
            $.get('/api/article')
                .done(response => dispatch({ type: LOAD_ALL_ARTICLES + SUCCESS, response }))
                .fail(error => dispatch({ type: LOAD_ALL_ARTICLES + FAIL, error }))
        }, 1000)

    }

Мы возвращаем функцию у которой будет доступ к dispatch, здесь мы скопировали часть логики из middlewares/api.js, чтобы не писать дважды, конечно же предварительно немного её изменив . Мы будем делать тоже самое действие, что и до этого, просто другим способом.

Также изменим import в этом же файле:

import { DELETE_ARTICLE, LOAD_ALL_ARTICLES, START, SUCCESS, FAIL } from '../constants'
import $ from 'jquery'

Не забудем подключить эту middleware в store, для этого перейдем в store/index.js и добавим:

import thunk from 'redux-thunk'

И поставим ее где-то в самом начале, так как все наши middleware ожидают какого-то объекта, а если они получат не объект а функцию, у нас могут появиться ошибки.

const enhancer = compose(
    applyMiddleware(thunk, dumbMiddleware, randomId, api, logger),
    window.devToolsExtension ? window.devToolsExtension() : f => f
)

А поставив redux-thunk первым номером он проверит –  приходит функция или нет, если да, то будет работать с ней, если нет, передаст управление в другие middlewares. Теперь убедимся, что у нас все работает, но предварительно  зайдем в containers/Articles.js и изменим один из import таким образом:

import { loadAllArticles, loadAllArticlesAlt } from '../AC/articles'

И в этом же файле в export defaults изменим функцию которую мы вызываем на loadAllArticlesAlt:

export default connect(({ articles, filters }) => {
    return {
        loading: articles.get('loading'),
        articles: filterArticles(articles.get('entities'), filters)
    }
}, {
    loadAllArticles: loadAllArticlesAlt
})(Articles)

Проверяем, все должно прекрасно работать! Посмотрите, мы пишем асинхронные actions, (к примеру, взгляните на код в AC/articles.js) прямо в action creator, хотя на самом деле, код который мы написали исполняется в middlewares. Здесь можно писать достаточно сложную логику, также у вас есть доступ к состоянию store:

export function loadAllArticlesAlt() {
    return (dispatch, state) => {
        dispatch({
            type: LOAD_ALL_ARTICLES + START
        })

Таким образом вы можете добавить что-то из store для реализации нужной вам логики.

Мы подключили redux-thunk вместе с остальными middleware в store, он проверяет тип action который к нему пришел. Когда вы пишите middleware Вам зачастую нужно проверить , что за action к Вам пришел, интересует ли его эта middleware или вам необходимо передать управление дальше. Мы делали проверки по флажку, к  примеру как в middlewares/randomId.js , а redux-thunkделает проверку по типу. Если к нему пришла функция, то он ее вызывает, примерно вот таким образом:action(next, store.getState()). (action это наша функция). А уже этот этот action (эту функцию) мы описали самостоятельно в Action Creator, в  AC/articles.js, а конкретно в loadAllArticlesAlt(), а redux-thunk ее вызывает, передав как аргумент состояние store и возможность dispatch  ваш action дальше. И мы этим пользуемся, мы берем и делаем dispatch над LOAD_ALL_ARTICLES + START в AC/articles.js:

export function loadAllArticlesAlt() {
    return (dispatch, state) => {
        dispatch({
            type: LOAD_ALL_ARTICLES + START
        })

Далее делаем асинхронные обращения к API, ждем чего-то, далее делаем еще один dispatch уже LOAD_ALL_ARTICLES + SUCCESS или FAIL . Пишем мы эту логику прямо в Action creator. Поскольку мы здесь имеем доступ к состоянию store , а также  возможность сделать dispatch нескольких actions, возможно они будут асинхронные и.т.п. – это то, что чаще всего нам нужно в middlewares .

 setTimeout(() => {
            $.get('/api/article')
                .done(response => dispatch({ type: LOAD_ALL_ARTICLES + SUCCESS, response }))
                .fail(error => dispatch({ type: LOAD_ALL_ARTICLES + FAIL, error }))
        }, 1000)

Таким образом redux-thunk позволяет нам не писать лишние middlewares. Если нам нужно реализовать какую-то сложную логику, которую вы  не будете переиспользовать (т.е. она будет использоваться в одном месте), вы можете просто обернуть это в функцию  добавив ее в Action Creator и далее использоватьredux-thunk

Если нужно выполнить отложенный вызов, то вы тоже делаете это в middleware. В Action creator такой опции нет, это просто чистая функция, которая возвращает объект. Также мы не можем сделать return через setTimeout. Поэтому Вам придется сделать что-то наподобие такого в вашем Action Creator AC/articles.js :

export function defferedDelete(id) {
    return (dispatch) => setTimeout(() => dispatch({type: DELETE_ARTICLE, payload: { id }}), 1000)
}

Почему redux-thunk все же не так хорош? Очень многие разработчики забывают о middlewares совсем. Наш совет состоит в том, чтобы помнить, что обращения к серверу делается в middlewares. Не стоит писать всю логику в action creators с использованием redux-thunk. Ну или нужно отдавать себе отчет, зачем вы это делаете. Нужно помнить, что actions должны всегда быть чистыми функциями, поэтому не стоит писать в них например генерацию случайного id.  Подобные решения ставят крест на дальнейшем переводе приложения в разряд тех где присутствует серверный рендеринг. В чем вся прелесть middlewares – в том что это централизованное место для всех Side Effects. Т.е. когда вы захотите перенести эту бизнес-логику на сервер или React-native, вы теоретически сможете использовать многое из уже существующей логики, тогда как изменениям может подвергнуться только сама middleware.

Домашнее задание – сделать загрузку комментариев, чтобы по клику на show comments комментарии у Вас загружались с сервера. Таким образом, загрузка комментариев будет выполняться с такого  адресу (к примеру) , где в конце находится id статьи:

http://localhost:8080/api/comment?article=56c782f18990ecf954f6e027

Код урока вы можете скачать здесь.

keep-calm14

 

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

2. Уроки Node.js. Модули Часть 2

Начнем урок с изучения объекта модуль, он содержит множество важных свойств, которые понадобиться в более сложных сценариях поведения.
Объект module это переменная, которая существует в каждом модуле.
Сделаем запись в index.js и выведем ее

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

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

Leave a Reply