Redux-Redents — (еще) один модуль для работы с серверными данными из React-Redux приложений.  

в 10:38, , рубрики: front-end development, front-end разработка, javascript, node.js, npm, React, ReactJS, redux, Разработка веб-сайтов

React и Redux, в последнее время одни из самых популярных buzz-words в мире фронтенда. Поэтому когда мне потребовалось сделать веб-приложение, которое бы отображало данные, полученные с сервера, а также позволяло бы ими манипулировать (создавать, удалять и изменять), я решил построить его на основе связки React и Redux. Множество getting-started руководств покрывают только функционал создания компонентов, action creators и reducers. Но как только дело касается обмена с сервером, начинаются сложности — растет количество необходимых action creator, редьюсеров. Причем они очень похожи друг на друга, с миниальными отличиями. В большинстве случаев — только в типе (имени) активности. После того, как я создал третий одинаковый набор креаторов и редьюсеров, то появилось желание что-то изменить. Так родилась идея реализации redux-redents.

Начало, dictionary reducers

В общем виде reducers очень похожи друг на друга — принять свой action и создать на его основе новое состояние хранилища. Если рассматривать reducers для обработки ответа с сервера, то они будут отличаться только типом action. Так родилась идея "универсального" редьюсера:

function createReducer(acttype,initialState) {
    return (state = initialState, action) => {
        if(action.type!=acttype) return state;
        return action.res.data;
    };
}

const dicts = {
  type1 : createReducer(TYPE1_CONSTANT,{}),
  type2 : createReducer(TYPE2_CONSTANT,[])
}

const rootReducer = combineReducers({...dicts});

Уже это позволяет не писать одинаковые функции и не городить switch-case конструкции.

Константы. Избавление.

Словарь редьюсеров сократил количество одинакового кода, но остались константы для задания типов action. Добавление нового action и его обработчика выглядит так:

  1. создать константу для типа action
  2. создать action creator
  3. создать обработчик createReducer с заданным типом.

Поддержка набора констант начинает раздражать практически сразу. Тем более, что смысла в них практически никакого нет — разработчик их использует только для связки creator и reducer. Таким образом константы можно заменить на соглашения о конфигурировании типов action.
Далее — все action creators для получения данных с сервера выглядят одинаково — создать action с нужным типом и promise для запроса на сервер. Если они выглядят одинаково, то не лучше ли автоматизировать процесс создания creators, а еще лучше сделать универсальный creator?

Объединение двух идей — замена констант соглашениями и универсальный creator и привели рождению модуля.

Соглашения о данных

Если для обмена с сервером используется rest-like api, то для каждого типа данных у нас есть одинаковое число операций по-умолчанию: index (list), get, post, delete; а у каждой операции есть uri и параметры для передачи на сервер. Таким образом можно заключить соглашения об умолчаниях:

  • каждый тип данных поддерживает стандартный набор операций
  • для каждой операции определены правила вычисления url и параметров

Кроме этого нужно предусмотреть возможность расширения:

  • добавление операций
  • конструирование запроса

В результате появился следующий формат:

entities = {
  fruit : {}, //all default
  vegetable: {
    crop : { //custom operation
      type: 'CROP_VEGETABLE',
      request: (data) => return post(url,data) //custom request
    },
    index: {
      url: url1 //custom url
    }
  }
}   

Универсальный action creator

Теперь для упрощения жизни осталось реализовать универсальный actions creator. И снова нам на помощь приходят соглашения:

  • стандартный набор операций — index, get, post, delete
  • правила вычисления url — url = base+entity+'s'
  • правила передачи параметров

    • get,delete — url = url+'/'+data
    • post — отправить data в теле запроса

    Универсальный action creator позволяет сделать следующее:

    this.props.entityOperation('fruit','index'); //загружает список фруктов
    this.props.entityOperation('fruit','get','apple'); //получает фрукт по имени 'apple'
    this.props.entityOperation('fruit','post',{name:'orange',id:'5'}); //обновляет или создает 'orange'
    this.props.entityOperation('vegetable','crop',{name:'parrot'}); //выполняет операцию crop над parrot

Creator создает action с promise для отправки/получения данных на сервер. Promise нужно как-то обработать и здесь на поиощь приходят redux middleware

Middlewares

Redux middleware — это функция, которая принимает action и следующий в цепочке обработчик и, которая может обработать action сама и/или передать его дальше по цепочке обработчиков. Для обработки promise нам нужно принять исходный action, установить обработчики promise и модифицировать action, чтобы показать что система находится в состоянии запроса данных к серверу. Для модификации можно либо добавлять поля к action, либо модифицировать ее тип. Я выбрал модификацию типа.

Promise Middleware

  • изменяет тип action.type = action.type+'_REQUEST';
  • создает обработчик успеха в promise переслать в следующий обработчик исходный action с ответом сервера
  • создает обработчик ошибки модифицировать тип - action.type=action.type+'_ERROR' и переслать в следующий обработчки ошибку, полученную с сервера
  • возвращает promise

Promises заработали, данные поступают с сервера, но стало не хватать возможности вызвать action после завершения выполнения другого. Например, обновить данные с сервера после сохранения данных на него же. Так была придумана Chain Middleware — функция, которая выполняет action creator после окончания обработки предыдущего action.

Chain Middleware

Для реализации цепочек вызовов, последним параметром к универсальному action creator была добавлена порождающая новый action функция, которая принимает на вход ответ сервера (если он существует) или исходный action (в противном случае).
Порождающая функция вызывается только в том, случае если обрабатываемый action содержит поле status со значением 'done' (action.status=='done')

this.entityOperation('fruit','save',fruit,()=>this.props.entityOperation('fruit','index'));

Module

Естесственным желаением было поделиться этими идеями и их реализацией — так родился модуль redux-redents. Модуль доступен к установке через npm

npm install --save redux-redents

Пример использования

В качестве примера разработано "приложение" client-demo

git clone https://github.com/kneradovsky/redents/
cd client-demo
npm install
npm start

последняя команда соберет приложение, запустит сервер разработки и откроет стартовый URL приложения в браузере
Приложение содержит одну страницу, которая отображает список фруктов и позволяет добавлять, удалять и редактировать фрукты. Вид страницы на скриншоте ниже:

screenshot

Заключение

Буду рад, если мой модуль окажется полезен. Открыт для вопросов, замечаний и предложений по расширению функциональности. Исходный код модуля, как всегда, доступен в GitHub репозитории

Автор: qualife

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js