Redux - Краткое руководство

Redux - это контейнер с предсказуемым состоянием для приложений JavaScript. По мере роста приложения становится трудно поддерживать его организованность и поддерживать поток данных. Redux решает эту проблему, управляя состоянием приложения с помощью одного глобального объекта под названием Store. Фундаментальные принципы Redux помогают поддерживать согласованность всего приложения, что упрощает отладку и тестирование.

Что еще более важно, он дает вам возможность редактирования кода в реальном времени в сочетании с отладчиком, путешествующим во времени. Он может гибко работать с любым уровнем представления, таким как React, Angular, Vue и т. Д.

Принципы Redux

Предсказуемость Redux определяется тремя наиболее важными принципами, приведенными ниже:

Единый источник истины

Состояние всего вашего приложения хранится в дереве объектов в одном хранилище. Поскольку все состояние приложения хранится в одном дереве, это упрощает отладку и ускоряет разработку.

Состояние только для чтения

Единственный способ изменить состояние - испустить действие, объект, описывающий, что произошло. Это означает, что никто не может напрямую изменить состояние вашего приложения.

Изменения вносятся с использованием чистых функций

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

Перед установкой Redux, we have to install Nodejs and NPM. Ниже приведены инструкции, которые помогут вам его установить. Вы можете пропустить эти шаги, если на вашем устройстве уже установлены Nodejs и NPM.

  • Визит https://nodejs.org/ и установите файл пакета.

  • Запустите установщик, следуйте инструкциям и примите лицензионное соглашение.

  • Перезагрузите устройство, чтобы запустить его.

  • Вы можете проверить успешность установки, открыв командную строку и набрав node -v. Это покажет вам последнюю версию Node в вашей системе.

  • Чтобы проверить, успешно ли установлен npm, введите npm –v, который вернет вам последнюю версию npm.

Чтобы установить redux, вы можете выполнить следующие шаги -

Выполните следующую команду в командной строке, чтобы установить Redux.

npm install --save redux

Чтобы использовать Redux с приложением реакции, вам необходимо установить дополнительную зависимость следующим образом:

npm install --save react-redux

Чтобы установить инструменты разработчика для Redux, вам необходимо установить следующее в качестве зависимости:

Выполните приведенную ниже команду в командной строке, чтобы установить Redux dev-tools.

npm install --save-dev redux-devtools

Если вы не хотите устанавливать инструменты разработки Redux и интегрировать их в свой проект, вы можете установить Redux DevTools Extension для Chrome и Firefox.

Предположим, что состояние нашего приложения описывается простым объектом с именем initialState который выглядит следующим образом -

const initialState = {
   isLoading: false,
   items: [],
   hasError: false
};

Каждый фрагмент кода в вашем приложении не может изменить это состояние. Чтобы изменить состояние, вам нужно отправить действие.

Что такое действие?

Действие - это простой объект, который описывает намерение вызвать изменение с помощью свойства типа. Он должен иметь свойство типа, которое сообщает, какой тип действия выполняется. Команда к действию выглядит следующим образом -

return {
   type: 'ITEMS_REQUEST', //action type
   isLoading: true //payload information
}

Действия и состояния удерживаются вместе функцией под названием Reducer. Действие отправляется с намерением вызвать изменение. Это изменение выполняется редуктором. Редуктор - единственный способ изменить состояние в Redux, сделав его более предсказуемым, централизованным и отлаживаемым. Функция редуктора, которая обрабатывает действие ITEMS_REQUEST, выглядит следующим образом:

const reducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}

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

Мы обсудим, как мы можем разделить редукторы и объединить их с хранилищем позже в этом руководстве.

Компоненты Redux следующие:

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

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

  • Действие отправляется, когда пользователь взаимодействует с приложением.

  • Функция корневого редуктора вызывается с текущим состоянием и отправленным действием. Корневой редуктор может разделить задачу между меньшими функциями редуктора, которые в конечном итоге вернут новое состояние.

  • Магазин уведомляет представление, выполняя свои функции обратного вызова.

  • Представление может получить обновленное состояние и повторно выполнить рендеринг.

Магазин - это неизменное дерево объектов в Redux. Хранилище - это контейнер состояния, в котором хранится состояние приложения. Redux может иметь только одно хранилище в вашем приложении. Всякий раз, когда в Redux создается хранилище, вам необходимо указать редуктор.

Давайте посмотрим, как мы можем создать магазин, используя createStoreметод из Redux. Необходимо импортировать пакет createStore из библиотеки Redux, которая поддерживает процесс создания магазина, как показано ниже -

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

Функция createStore может иметь три аргумента. Ниже приводится синтаксис -

createStore(reducer, [preloadedState], [enhancer])

Редуктор - это функция, которая возвращает следующее состояние приложения. PreloadedState - это необязательный аргумент и начальное состояние вашего приложения. Усилитель также является необязательным аргументом. Это поможет вам расширить магазин за счет сторонних возможностей.

У магазина есть три важных метода, как указано ниже:

getState

Это помогает вам получить текущее состояние вашего магазина Redux.

Синтаксис getState следующий:

store.getState()

отправка

Это позволяет вам отправить действие для изменения состояния в вашем приложении.

Синтаксис для отправки следующий -

store.dispatch({type:'ITEMS_REQUEST'})

подписываться

Это помогает вам зарегистрировать обратный вызов, который хранилище Redux будет вызывать при отправке действия. Как только состояние Redux будет обновлено, представление будет автоматически отрисовано заново.

Синтаксис для отправки следующий -

store.subscribe(()=>{ console.log(store.getState());})

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

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Действия - единственный источник информации для магазина согласно официальной документации Redux. Он передает полезную информацию из вашего приложения в хранилище.

Как обсуждалось ранее, действия представляют собой простой объект JavaScript, который должен иметь атрибут type, чтобы указать тип выполняемого действия. Он рассказывает нам о том, что произошло. Типы должны быть определены как строковые константы в вашем приложении, как указано ниже -

const ITEMS_REQUEST = 'ITEMS_REQUEST';

За исключением атрибута типа, структура объекта действия полностью зависит от разработчика. Рекомендуется, чтобы ваш объект действия был как можно более легким и передавал только необходимую информацию.

Чтобы вызвать какие-либо изменения в магазине, вам нужно сначала отправить действие с помощью функции store.dispatch (). Объект действия следующий -

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

Создатели действий

Создатели действий - это функции, которые инкапсулируют процесс создания объекта действия. Эти функции просто возвращают простой объект Js, который является действием. Это способствует написанию чистого кода и помогает добиться повторного использования.

Давайте узнаем о создателе действий, который позволяет вам отправлять действие, ‘ITEMS_REQUEST’который запрашивает данные списка товаров с сервера. Между темisLoading состояние становится истинным в редукторе в типе действия ITEMS_REQUEST, чтобы указать, что элементы загружаются, а данные все еще не получены от сервера.

Первоначально состояние isLoading было ложным в initialStateобъект, если ничего не загружается. Когда данные получены в браузере, состояние isLoading будет возвращено как false в типе действия ITEMS_REQUEST_SUCCESS в соответствующем редукторе. Это состояние можно использовать как опору в компонентах реакции для отображения загрузчика / сообщения на вашей странице, пока запрос данных включен. Создатель действия выглядит следующим образом -

const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;
export function itemsRequest(bool,startIndex,endIndex) {
   let payload = {
      isLoading: bool,
      startIndex,
      endIndex
   }
   return {
      type: ITEMS_REQUEST,
      payload
   }
}
export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Чтобы вызвать функцию диспетчеризации, вам необходимо передать действие в качестве аргумента функции диспетчеризации.

dispatch(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

Вы можете отправить действие напрямую, используя store.dispatch (). Однако более вероятно, что вы получите доступ к нему с помощью вспомогательного метода response-Redux, называемогоconnect(). Вы также можете использоватьbindActionCreators() метод для привязки многих создателей действий с функцией отправки.

Функция - это процесс, который принимает входные данные, называемые аргументами, и производит некоторые выходные данные, известные как возвращаемое значение. Функция называется чистой, если она подчиняется следующим правилам:

  • Функция возвращает тот же результат для тех же аргументов.

  • Его оценка не имеет побочных эффектов, т.е. не изменяет входные данные.

  • Никаких изменений локальных и глобальных переменных.

  • Он не зависит от внешнего состояния, как глобальная переменная.

Давайте рассмотрим пример функции, которая возвращает дважды значение, переданное в качестве входных данных функции. Обычно это записывается как, f (x) => x * 2. Если функция вызывается со значением аргумента 2, то на выходе будет 4, f (2) => 4.

Давайте напишем определение функции в JavaScript, как показано ниже -

const double = x => x*2; // es6 arrow function
console.log(double(2));  // 4

Here, double is a pure function.

Согласно трем принципам Redux, изменения должны выполняться чистой функцией, то есть редуктором в Redux. Теперь возникает вопрос, почему редуктор должен быть чистой функцией.

Предположим, вы хотите отправить действие, тип которого 'ADD_TO_CART_SUCCESS' чтобы добавить товар в приложение корзины покупок, нажав кнопку «Добавить в корзину».

Предположим, редуктор добавляет товар в вашу корзину, как показано ниже -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         state.isAddedToCart = !state.isAddedToCart; //original object altered
         return state;
      default:
         return state;
   }
}
export default addToCartReducer ;

Давайте предположим, isAddedToCart - это свойство объекта состояния, которое позволяет вам решить, когда отключить кнопку «добавить в корзину» для элемента, возвращая логическое значение ‘true or false’. Это не позволяет пользователю добавлять один и тот же продукт несколько раз. Теперь вместо того, чтобы возвращать новый объект, мы изменяем свойство isAddedToCart в состоянии, как указано выше. Теперь, если мы попытаемся добавить товар в корзину, ничего не произойдет. Кнопка «Добавить в корзину» не будет отключена.

Причина такого поведения заключается в следующем -

Redux сравнивает старые и новые объекты по месту в памяти обоих объектов. Он ожидает новый объект от редуктора, если произошло какое-либо изменение. И он также ожидает вернуть старый объект, если не произойдет никаких изменений. В данном случае это то же самое. По этой причине Redux предполагает, что ничего не произошло.

Итак, редуктор должен быть чистой функцией Redux. Ниже приведен способ записать его без мутаций -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         return {
            ...state,
            isAddedToCart: !state.isAddedToCart
         }
      default:
         return state;
   }
}
export default addToCartReducer;

Редукторы - это чистая функция Redux. Чистые функции предсказуемы. Редукторы - единственный способ изменить состояние в Redux. Это единственное место, где можно писать логику и расчеты. Функция Reducer примет предыдущее состояние приложения и отправляемого действия, вычислит следующее состояние и вернет новый объект.

Следующие несколько вещей никогда не должны выполняться внутри редуктора:

  • Мутация аргументов функций
  • Вызовы API и логика маршрутизации
  • Вызов нечистой функции, например Math.random ()

Ниже приведен синтаксис редуктора -

(state,action) => newState

Продолжим пример отображения списка товаров на веб-странице, рассмотренный в модуле создателей действий. Давайте посмотрим ниже, как написать его редуктор.

const initialState = {
   isLoading: false,
   items: []
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      case ‘ITEMS_REQUEST_SUCCESS':
         return Object.assign({}, state, {
            items: state.items.concat(action.items),
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Во-первых, если вы не установите состояние 'initialState', Redux вызовет reducer с неопределенным состоянием. В этом примере кода функция concat () JavaScript используется в 'ITEMS_REQUEST_SUCCESS', которая не изменяет существующий массив; вместо этого возвращает новый массив.

Таким образом можно избежать мутации состояния. Никогда не пишите напрямую государству. В 'ITEMS_REQUEST' мы должны установить значение состояния из полученного действия.

Уже обсуждалось, что мы можем написать нашу логику в редукторе и разбить ее на основе логических данных. Давайте посмотрим, как мы можем разделить редукторы и объединить их вместе в качестве корневого редуктора при работе с большим приложением.

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

/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;
export default function (state = {} , action) {
   switch(action.type) {
      case GET_ORDER_STATUS:
         return { ...state, orderStatusData: action.payload.orderStatus };
      default:
         return state;
   }
}

Точно так же предположим, что действие GET_WISHLIST_ITEMS отправлено для получения информации о списке желаний пользователя, относящейся к пользователю.

/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;
export default function (state = {}, action) {
   switch(action.type) {
      case GET_WISHLIST_ITEMS:
         return { ...state, wishlistData: action.payload.wishlistData };
      default:
         return state;
   }
}

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

/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;

const rootReducer = combineReducers ({
   orderStatusReducer: OrderStatusReducer,
   getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;

Теперь вы можете передать этот rootReducer методу createStore следующим образом:

const store = createStore(rootReducer);

Сам Redux является синхронным, поэтому как async такие операции, как network requestработать с Redux? Здесь могут пригодиться промежуточные программы. Как обсуждалось ранее, редукторы - это место, где записывается вся логика выполнения. Редуктор не имеет ничего общего с тем, кто его выполняет, сколько времени он занимает или регистрирует состояние приложения до и после отправки действия.

В этом случае функция промежуточного программного обеспечения Redux предоставляет среду для взаимодействия с диспетчеризацией действий до того, как они достигнут редуктора. Настраиваемые функции промежуточного программного обеспечения могут быть созданы путем написания функций высокого порядка (функция, возвращающая другую функцию), которые охватывают некоторую логику. Несколько промежуточных программ могут быть объединены вместе для добавления новых функций, и каждое промежуточное программное обеспечение не требует знания того, что было до и после. Вы можете представить себе промежуточное ПО где-то между отправленным действием и редуктором.

Обычно промежуточное ПО используется для обработки асинхронных действий в вашем приложении. Redux предоставляет API под названием applyMiddleware, который позволяет нам использовать собственное промежуточное ПО, а также промежуточное ПО Redux, такое как redux-thunk и redux-prom. Он применяет промежуточное ПО для хранения. Синтаксис использования API applyMiddleware:

applyMiddleware(...middleware)

И это можно применить к хранилищу следующим образом -

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));

Промежуточное ПО позволит вам написать диспетчер действий, который возвращает функцию вместо объекта действия. Пример того же показан ниже -

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

Условная отправка может быть написана внутри промежуточного программного обеспечения. Каждое промежуточное программное обеспечение получает отправку хранилища, чтобы они могли отправлять новое действие, и функции getState в качестве аргументов, чтобы они могли получить доступ к текущему состоянию и вернуть функцию. Любое возвращаемое значение из внутренней функции будет доступно как значение самой функции диспетчеризации.

Ниже приведен синтаксис промежуточного программного обеспечения -

({ getState, dispatch }) => next => action

Функция getState полезна, чтобы решить, следует ли извлекать новые данные или возвращать результат кеширования, в зависимости от текущего состояния.

Давайте посмотрим на пример настраиваемой функции регистратора промежуточного программного обеспечения. Он просто регистрирует действие и новое состояние.

import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'

function logger({ getState }) {
   return next => action => {
      console.log(‘action’, action);
      const returnVal = next(action);
      console.log('state when action is dispatched', getState()); 
      return returnVal;
   }
}

Теперь примените промежуточное ПО регистратора к магазину, написав следующую строку кода -

const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));

Отправьте действие, чтобы проверить отправленное действие и новое состояние, используя приведенный ниже код -

store.dispatch({
   type: 'ITEMS_REQUEST', 
	isLoading: true
})

Другой пример промежуточного программного обеспечения, в котором вы можете управлять, когда показывать или скрывать загрузчик, приведен ниже. Это промежуточное ПО показывает загрузчик, когда вы запрашиваете какой-либо ресурс, и скрывает его, когда запрос ресурса завершен.

import isPromise from 'is-promise';

function loaderHandler({ dispatch }) {
   return next => action => {
      if (isPromise(action)) {
         dispatch({ type: 'SHOW_LOADER' });
         action
            .then(() => dispatch({ type: 'HIDE_LOADER' }))
            .catch(() => dispatch({ type: 'HIDE_LOADER' }));
      }
      return next(action);
   };
}
const store = createStore(
   userLogin , initialState = [ ] , 
   applyMiddleware(loaderHandler)
);

Redux-Devtools предоставляет нам платформу для отладки приложений Redux. Это позволяет нам выполнять отладку во времени и редактировать в реальном времени. Некоторые из функций в официальной документации следующие:

  • Он позволяет вам проверять каждое состояние и полезную нагрузку действия.

  • Позволяет вернуться во времени, «отменяя» действия.

  • Если вы измените код редуктора, каждое «поэтапное» действие будет повторно оцениваться.

  • Если редукторы выдают ошибку, мы можем определить ошибку, а также определить, во время какого действия это произошло.

  • С помощью средства улучшения хранилища persistState () вы можете сохранять сеансы отладки при перезагрузке страницы.

Существует два варианта инструментов разработки Redux, как показано ниже -

Redux DevTools - Его можно установить как пакет и интегрировать в ваше приложение, как указано ниже -

https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration

Redux DevTools Extension - Расширение браузера, которое реализует те же инструменты разработчика для Redux, выглядит следующим образом:

https://github.com/zalmoxisus/redux-devtools-extension

Теперь давайте проверим, как можно пропустить действия и вернуться во времени с помощью инструмента Redux dev. Следующие снимки экрана объясняют действия, которые мы отправили ранее для получения списка товаров. Здесь мы можем увидеть действия, отправленные на вкладке инспектора. Справа вы можете увидеть вкладку Demo, которая показывает разницу в дереве состояний.

Вы познакомитесь с этим инструментом, когда начнете его использовать. Вы можете отправить действие без написания фактического кода только из этого плагина Redux. В этом вам поможет опция «Диспетчер» в последней строке. Давайте проверим последнее действие, при котором элементы были успешно извлечены.

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

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

Нажав на кнопку пропуска для определенного типа действия, вы можете пропустить конкретное действие. Он действует так, как будто действия никогда не было. Когда вы нажимаете кнопку перехода для определенного типа действия, он переводит вас в состояние, когда это действие произошло, и последовательно пропускает все оставшиеся действия. Таким образом, вы сможете сохранить состояние, в котором произошло определенное действие. Эта функция полезна при отладке и поиске ошибок в приложении.

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

Тестировать код Redux легко, поскольку мы в основном пишем функции, и большинство из них чистые. Так что мы можем протестировать это, даже не высмеивая их. Здесь мы используем JEST в качестве механизма тестирования. Он работает в среде узлов и не имеет доступа к DOM.

Мы можем установить JEST с кодом, приведенным ниже -

npm install --save-dev jest

С babel вам нужно установить babel-jest следующим образом -

npm install --save-dev babel-jest

И настройте его для использования функций babel-preset-env в файле .babelrc следующим образом:

{ 
   "presets": ["@babel/preset-env"] 
}
And add the following script in your package.json:
{ 
   //Some other code 
   "scripts": {
      //code
      "test": "jest", 
      "test:watch": "npm test -- --watch" 
   }, 
   //code 
}

В заключение, run npm test or npm run test. Давайте посмотрим, как мы можем написать тестовые примеры для создателей действий и редукторов.

Тестовые примеры для создателей действий

Предположим, у вас есть создатель действий, как показано ниже -

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Этот создатель действия можно протестировать, как указано ниже -

import * as action from '../actions/actions';
import * as types from '../../constants/ActionTypes';

describe('actions', () => {
   it('should create an action to check if item is loading', () => { 
      const isLoading = true, 
      const expectedAction = { 
         type: types.ITEMS_REQUEST_SUCCESS, isLoading 
      } 
      expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction) 
   })
})

Тестовые примеры для редукторов

Мы узнали, что редуктор должен возвращать новое состояние при применении действия. Итак, reducer протестирован на этом поведении.

Рассмотрим редуктор, как указано ниже -

const initialState = {
   isLoading: false
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Чтобы протестировать вышеуказанный редуктор, нам нужно передать состояние и действие редуктору и вернуть новое состояние, как показано ниже -

import reducer from '../../reducer/reducer' 
import * as types from '../../constants/ActionTypes'

describe('reducer initial state', () => {
   it('should return the initial state', () => {
      expect(reducer(undefined, {})).toEqual([
         {
            isLoading: false,
         }
      ])
   })
   it('should handle ITEMS_REQUEST', () => {
      expect(
         reducer(
            {
               isLoading: false,
            },
            {
               type: types.ITEMS_REQUEST,
               payload: { isLoading: true }
            }
         )
      ).toEqual({
         isLoading: true
      })
   })
})

Если вы не знакомы с написанием тестовых примеров, вы можете изучить основы JEST .

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

Скажем, нужно ли различным реагирующим компонентам отображать одни и те же данные по-разному, не передавая их в качестве опоры всем компонентам от компонента верхнего уровня до самого нижнего. Было бы идеально хранить его вне реагирующих компонентов. Потому что это помогает в более быстром извлечении данных, поскольку вам не нужно полностью передавать данные различным компонентам.

Давайте обсудим, как это возможно с Redux. Redux предоставляет пакет response-redux для связывания компонентов реакции с двумя утилитами, как указано ниже:

  • Provider
  • Connect

Провайдер делает магазин доступным для остальной части приложения. Функция Connect помогает компоненту реагировать на подключение к магазину, реагируя на каждое изменение, происходящее в состоянии магазина.

Давайте посмотрим на root index.js файл, который создает хранилище и использует поставщика, который включает хранилище для остальной части приложения в приложении response-redux.

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers/reducer'
import thunk from 'redux-thunk';
import App from './components/app'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
   applyMiddleware(thunk)
)
render(
   <Provider store = {store}>
      <App />
   </Provider>,
   document.getElementById('root')
)

Каждый раз, когда в приложении response-redux происходит изменение, вызывается mapStateToProps (). В этой функции мы точно указываем, какое состояние нужно предоставить нашему реагирующему компоненту.

С помощью функции connect (), описанной ниже, мы связываем состояние этого приложения с реагирующим компонентом. Connect () - это функция высокого порядка, которая принимает компонент в качестве параметра. Он выполняет определенные операции и возвращает новый компонент с правильными данными, которые мы наконец экспортировали.

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

import { connect } from 'react-redux'
import Listing from '../components/listing/Listing' //react component
import makeApiCall from '../services/services' //component to make api call

const mapStateToProps = (state) => {
   return {
      items: state.items,
      isLoading: state.isLoading
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      fetchData: () => dispatch(makeApiCall())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Listing);

Определение компонента для вызова API в файле services.js выглядит следующим образом:

import axios from 'axios'
import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions'

export default function makeApiCall() {
   return (dispatch) => {
      dispatch(itemsLoading(true));
      axios.get('http://api.tvmaze.com/shows')
      .then((response) => {
         if (response.status !== 200) {
            throw Error(response.statusText);
         }
         dispatch(itemsLoading(false));
         return response;
      })
      .then((response) => dispatch(itemsFetchDataSuccess(response.data)))
   };
}

Функция mapDispatchToProps () принимает функцию диспетчеризации в качестве параметра и возвращает вам реквизиты обратного вызова в виде простого объекта, который вы передаете своему компоненту реакции.

Здесь вы можете получить доступ к fetchData как к опоре в вашем компоненте листинга реакции, который отправляет действие для вызова API. mapDispatchToProps () используется для отправки действия в хранилище. В react-redux компоненты не могут напрямую обращаться к хранилищу. Единственный способ - использовать connect ().

Давайте разберемся, как работает react-redux, через диаграмму ниже -

STORE - Сохраняет все состояние вашего приложения как объект JavaScript

PROVIDER - Делает магазины доступными

CONTAINER - Получить состояние приложения и предоставить его в качестве опоры для компонентов

COMPONENT - Пользователь взаимодействует через компонент просмотра

ACTIONS - Вызывает изменение в магазине, это может или не может изменить состояние вашего приложения

REDUCER - Единственный способ изменить состояние приложения, принять состояние и действие и вернуть обновленное состояние.

Однако Redux является независимой библиотекой и может использоваться с любым уровнем пользовательского интерфейса. React-redux - это официальный Redux, привязка пользовательского интерфейса к response. Более того, это способствует хорошей реакции на структуру приложения Redux. React-redux внутренне реализует оптимизацию производительности, поэтому повторный рендеринг компонента происходит только тогда, когда это необходимо.

Подводя итог, Redux не предназначен для написания самого короткого и самого быстрого кода. Он предназначен для обеспечения предсказуемого контейнера управления состоянием. Это помогает нам понять, когда изменилось определенное состояние или откуда пришли данные.

Вот небольшой пример приложения React и Redux. Вы также можете попробовать разработать небольшие приложения. Пример кода для увеличения или уменьшения счетчика приведен ниже -

Это корневой файл, который отвечает за создание хранилища и рендеринг нашего компонента реагирующего приложения.

/src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducer from '../src/reducer/index'
import App from '../src/App'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && 
   window.__REDUX_DEVTOOLS_EXTENSION__()
)
render(
   <Provider store = {store}>
      <App />
   </Provider>, document.getElementById('root')
)

Это наш корневой компонент реакции. Он отвечает за рендеринг компонента контейнера счетчика в качестве дочернего.

/src/app.js

import React, { Component } from 'react';
import './App.css';
import Counter from '../src/container/appContainer';

class App extends Component {
   render() {
      return (
         <div className = "App">
            <header className = "App-header">
               <Counter/>
            </header>
         </div>
      );
   }
}
export default App;

Ниже приведен компонент контейнера, который отвечает за предоставление состояния Redux для реагирования компонента:

/container/counterContainer.js

import { connect } from 'react-redux'
import Counter from '../component/counter'
import { increment, decrement, reset } from '../actions';

const mapStateToProps = (state) => {
   return {
      counter: state
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      increment: () => dispatch(increment()),
      decrement: () => dispatch(decrement()),
      reset: () => dispatch(reset())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Ниже приведен компонент реакции, отвечающий за часть просмотра -

/component/counter.js
import React, { Component } from 'react';
class Counter extends Component {
   render() {
      const {counter,increment,decrement,reset} = this.props;
      return (
         <div className = "App">
            <div>{counter}</div>
            <div>
               <button onClick = {increment}>INCREMENT BY 1</button>
            </div>
            <div>
               <button onClick = {decrement}>DECREMENT BY 1</button>
            </div>
            <button onClick = {reset}>RESET</button>
         </div>
      );
   }
}
export default Counter;

Ниже приведены создатели действий, ответственные за создание действия:

/actions/index.js
export function increment() {
   return {
      type: 'INCREMENT'
   }
}
export function decrement() {
   return {
      type: 'DECREMENT'
   }
}
export function reset() {
   return { type: 'RESET' }
}

Ниже мы показали строку кода для файла редуктора, который отвечает за обновление состояния в Redux.

reducer/index.js
const reducer = (state = 0, action) => {
   switch (action.type) {
      case 'INCREMENT': return state + 1
      case 'DECREMENT': return state - 1
      case 'RESET' : return 0 default: return state
   }
}
export default reducer;

Изначально приложение выглядит следующим образом -

Когда я нажимаю кнопку увеличения два раза, экран вывода будет таким, как показано ниже -

Когда мы уменьшаем его один раз, он показывает следующий экран -

Сброс вернет приложение в исходное состояние, которое является значением счетчика 0. Это показано ниже -

Давайте разберемся, что происходит с инструментами разработки Redux, когда происходит первое действие приращения -

Состояние приложения будет перемещено в то время, когда отправляется только действие приращения, а остальные действия пропускаются.

Мы рекомендуем самостоятельно разработать небольшое приложение Todo и лучше понять инструмент Redux.