Введение

Redux - это контейнер с предсказуемым состоянием для приложений JavaScript. Его можно разделить на 3 части.

  1. Для приложений JavaScript: - Он не привязан к React. Его можно использовать с React, Angular, Vue и т. Д.
  2. Контейнер состояний: - Redux хранит состояние приложения, которое представляет собой состояние, представленное всеми отдельными компонентами этого приложения, включая данные и логику пользовательского интерфейса.
  3. Предсказуемость: - Все переходы между состояниями являются явными, и их можно отслеживать.

React-Redux

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

React ← → React-Redux ← → Redux

Давайте обсудим основные концепции Redux. Затем мы посмотрим, как связать react и redux.

Три основных концепции

  1. Магазин: - Сохраняет состояние вашего приложения.
  2. Действие: - Описывает, что произошло.
  3. Редуктор: - Связывает магазин и действия вместе.

Три принципа

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

Действие - это объект со свойством type.

Редукторы принимают prevState и action в качестве параметров и возвращают новое состояние как единый объект.

Redux Store - это единый магазин для приложения. Обязанности состоят из:

а. Сохраняет состояние приложения.

б. Разрешает доступ к состоянию через getState ().

c. Позволяет обновлять состояние через отправку (действие).

d. Зарегистрируйте слушателей через подписку (listener).

е. Обрабатывает отмену регистрации слушателей с помощью функции, возвращаемой подпиской (слушателем).

Возьмем пример приложения. Это приложение магазина по продаже тортов и мороженого.

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

  1. Создайте приложение React.
  2. Определите действия и создателей действий.
  3. Определите редукторы.
  4. Создайте магазин redux и предоставьте его приложению React через компонент Provider.
  5. Подключите действия состояния и отправки к приложению React.

Для создания приложения нам необходимо сохранить наилучшую структуру папок. Особых правил для структуры папок нет.

В этом приложении у нас есть 2 компонента. CakeContainer и IceCreamContainer.

Затем мы сохраним папку для каждого компонента. Эта папка используется для определения констант типа действия, действия и редуктора.

Внутри папки с тортами у нас будет 3 файла.

  1. cakeTypes.js

Используется для определения типа действий как констант.

export const BUY_CAKE = 'BUY_CAKE'

2. cakeActions.js

Мы определим создателя действия, который возвращает действие в этом файле. Действие - это объект со свойством type. Операции экспорта выполняются в общей папке с именем index.js (нет строгих правил для структуры папок).

import {BUY_CAKE} from './cakeTypes'
export const buyCake = () => {
return {
    type: BUY_CAKE
    }
}

3. cakeReducers.js

Теперь мы определяем редуктор, который объясняет, как обрабатывать действие. Reducer - это чистая функция, которая принимает в качестве входных данных prevState и action и возвращает следующее состояние.

import {BUY_CAKE} from './cakeTypes'
const initialState = {
      numOfCakes: 10
}
const cakeReducer = (state = initialState, action) => {
     switch(action.type){
     case BUY_CAKE: return{
         ...state,
         numOfCakes: state.numOfCakes - 1
     }
     default: return state
   }
}
export default cakeReducer

Аналогичным образом мы определяем типы, действия и редукторы для компонента IceCreamContainer.

  1. iceCreamTypes.js
export const BUY_ICECREAM = 'BUY_ICECREAM'

2. iceCreamActions.js

import {BUY_ICECREAM} from './iceCreamTypes'
export const buyIceCream = () => {
     return {
        type: BUY_ICECREAM
     }
}

3. iceCreamReducer.js.

import {BUY_ICECREAM} from './iceCreamTypes'
const initialState = {
       numOfIceCreams: 20
}
const iceCreamReducer = (state = initialState, action) => {
      switch(action.type){
      case BUY_ICECREAM: return{
           ...state,
           numOfIceCreams: state.numOfIceCreams - 1
     }
     default: return state
   }
}
export default iceCreamReducer

index.js

export {buyCake} from './cake/cakeActions'
export {buyIceCream} from './ice cream/iceCreamActions'

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

До этого мы использовали combReducers, чтобы объединить все редукторы в один корневой редуктор. Поскольку параметр createStore должен быть единственным редуктором.

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

rootReducer.js

import {combineReducers} from 'redux'
import cakeReducer from './cake/cakeReducer'
import iceCreamReducer from './ice cream/iceCreamReducers'
export const rootReducer = combineReducers({
             cake: cakeReducer,
             iceCream: iceCreamReducer
})

Создайте хранилище и передайте в качестве аргумента единственный корневой редуктор.

store.js

import {createStore} from 'redux'
import {rootReducer} from './rootReducer'
const store = createStore(rootReducer)
export default store

Это хранилище должно быть предоставлено компоненту Приложение с помощью тега Provider и атрибута store. И теги компонентов должны находиться внутри этого тега поставщика.

import {Provider} from 'react-redux'
import store from './redux/store'
import CakeContainer from './components/CakeContainer'
import IceCreamContainer from './components/IceCreamContainer'
function App() {
    return (
     <Provider store={store}>
        <div className="App">
             <CakeContainer />
             <IceCreamContainer />
        </div>
     </Provider>
    )
}
export default App

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

1-я функция - mapStateToProps: - входным аргументом будет состояние и будет возвращен объект, который сопоставляет состояние со свойством.

Вторая функция - mapDispatchToProps: - входным аргументом будет метод отправки и возврат объекта, который отправляет создателя действия из redux.

Затем нам нужно подключить обе функции к компоненту. Это достигается с помощью connect от react-redux.

export default connect(mapStateToProps,mapDispatchToProps)(CakeContainer);

И теперь мы можем получить доступ к действиям состояния и диспетчеризации, используя реквизиты в функциональных компонентах CakeContainer и IceCreamContainer.

CakeContainer.js

import React from 'react'
import {connect} from 'react-redux'
import {buyCake} from '../redux'
function CakeContainer(props) {
    return(
      <div>
        <h2> Number of Cakes - {props.numOfCakes}</h2>
        <button onClick={props.buyCake}>Buy cake</button>
      </div>
    )
}
const mapStateToProps = (state) => {
      return{ numOfCakes: state.cake.numOfCakes}
}
const mapDispatchToProps = (dispatch) => {
      return {buyCake: () => dispatch(buyCake())}
}
export default connect(mapStateToProps,mapDispatchToProps)( CakeContainer)

IceCreamContainer.js

import React from 'react'
import {connect} from 'react-redux'
import {buyIceCream} from '../redux'
function IceCreamContainer(props) {
    return(
      <div>
        <h2> Number of IceCreams - {props.numOfIceCreams}</h2>      <button onClick={props.buyIceCream}>Buy IceCream</button>
     </div>
    )
}
const mapStateToProps = (state) => {
      return{ numOfIceCreams: state.iceCream.numOfIceCreams}
}
const mapDispatchToProps = (dispatch) => {
      return {buyIceCream: () => dispatch(buyIceCream())}
}
export default connect(mapStateToProps,mapDispatchToProps)( IceCreamContainer)

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

  1. установить redux logger: - npm i redux-logger
  2. регистратор импорта
  3. передать регистратор вместе с applyMiddleware для хранения
import {createStore, applyMiddleware} from 'redux'
import logger from 'redux-logger'
import {rootReducer} from './rootReducer'
const store = createStore(rootReducer,applyMiddleware(logger))
export default store

Расширение Redux DevTools: - Это очень полезно в приложении redux. Особенно в отладке. Во-первых, нам нужно добавить расширение в Chrome. Затем мы можем использовать панель из элемента проверки инструментов разработки.

Некоторые из его особенностей: -

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

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

c. Затем мы можем использовать левую панель для любых последующих действий, и эта информация поможет нам в значительной степени отладить.

Теперь давайте обсудим дополнительную информацию, которую можно добавить к объекту действия. В приведенном выше примере мы обсуждали действие, имеющее только свойство type. Мы можем определить любое количество свойств в объекте действия в соответствии с нашим требованием.

Например, если нам нужно купить определенное количество тортов или мороженого, мы можем предоставить тег input, где мы можем определить, сколько тортов требуется, затем мы можем вычесть это значение из numOfCakes и, таким образом, обновить штат.

Изменения в коде: -

  1. cakeTypes.js

Поскольку новые действия не добавляются, в файле нет изменений типа действия.

2. cakeActions.js

import {BUY_CAKE} from './cakeTypes'
export const buyCake = (number = 1) => {
       return {
         type: BUY_CAKE,
         payload: number
       }
}

3. cakeReducers.js

import {BUY_CAKE} from './cakeTypes'
const initialState = {
      numOfCakes: 10
}
const cakeReducer = (state = initialState, action) => {
switch(action.type){
          case BUY_CAKE: return{
               ...state,
               numOfCakes: state.numOfCakes - action.payload
          }
      default: return state
      }
}
export default cakeReducer

NewCakeContainer.js

import React,{useState} from 'react'
import {connect} from 'react-redux'
import {buyCake} from '../redux'
function NewCakeContainer(props) {
    const [number,setNumber] = useState(1)
    return(
      <div>
        <h2> Number of Cakes - {props.numOfCakes}</h2>
        <input type='text' value={number} onChange={e => setNumber(e.target.value)}/>
        <button onClick={() => props.buyCake(number)}>Buy {number} Cakes</button>
</div>
    )
}
const mapStateToProps = (state) => {
      return{ numOfCakes: state.cake.numOfCakes}
}
const mapDispatchToProps = (dispatch) => {
      return {buyCake: (number) => dispatch(buyCake(number))}
}
export default connect(mapStateToProps,mapDispatchToProps)( NewCakeContainer)

Асинхронные действия

Во-первых, нам нужно узнать о действиях. Есть 2 типа действий.

  1. Синхронные действия: - Как только действие отправлено, состояние немедленно обновляется.

Если вы отправляете действие BUY_CAKE, numOfCakes сразу же уменьшается на 1.

2. Асинхронные действия: - Асинхронные вызовы API для извлечения данных из конечной точки и использования этих данных в вашем приложении.

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

Штат

state = {
      loading: true,
      data: [],
      error: ''
}

loading: - отобразить счетчик нашего компонента

data: - список пользователей

error: - ошибка пользовательского интерфейса

Действия

FETCH_USERS_REQUEST //request to fetch list of users
FETCH_USERS_SUCCESS //fetched successfully
FETCH_USERS-FAILURE // error fetching data

Редукторы

case FETCH_USERS_REQUEST:
     loading:true
case FETCH_USERS_SUCCESS:
     loading:false
     users:data (from API)
case FETCH_USERS-FAILURE:
     loading:false
     error: error (from API)

Создайте компонент (UserContainer) для отображения списка пользователей.

Затем создайте папку в redux для типов, действий и редукторов.

  1. userTypes.js
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'

2. userActions.js

import {FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS,FETCH_USERS_FAILURE} from './userTypes'
export const fetchUSersRequest = () => {
      return {
        type: FETCH_USERS_REQUEST
      }
}
export const fetchUsersSuccess = (users) => {
      return {
        type: FETCH_USERS_SUCCESS,
        payload: users
      }
}
export const fetchUsersFailure = (error) => {
      return {
        type: FETCH_USERS_FAILURE,
        payload: error
      }
}

3. userReducers.js

import {FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS,FETCH_USERS_FAILURE} from './userTypes'
const initialState = {
      loading: false,
      users: [],
      error: ''
}
const userReducer = ( state = initialState, action) => {                      switch(action.type){
case FETCH_USERS_REQUEST:
     return{
       ...state,
       loading: true
     }
case FETCH_USERS_SUCCESS:
     return{
       loading: false,
       users: action.payload,
       error: ''
     }
case FETCH_USERS_FAILURE:
     return{
       loading:false,
       users: '',
       error: action.payload
     }
}
}
export default userReducer

4. Измененный файл rootReducer.js.

import {combineReducers} from 'redux'
import cakeReducer from './cake/cakeReducer'
import iceCreamReducer from './ice cream/iceCreamReducers'
import {userReducer} from './user/userReducers'
export const rootReducer = combineReducers({
       cake: cakeReducer,
       iceCream: iceCreamReducer,
       user: userReducer
})

Затем, как сделать запрос на получение к конечной точке API и отобразить полученные данные в пользовательском интерфейсе. Для этого нам нужно установить 2 пакета.

  1. Axios: используется для получения запросов к конечной точке API.
  2. Redux-thunk: он позволит как определять создателей асинхронных действий в нашем приложении.

Шаги

  1. Установите необходимые пакеты.
npm install axios redux-thunk

2. Примените промежуточное ПО redux-thunk к хранилищу redux.

Измененный store.js

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import {rootReducer} from './rootReducer'
const store = createStore(rootReducer,applyMiddleware(logger,thunk))
export default store

3. Определите функцию для выборки пользователей в userActions.js.

Эта функция отличается от действий в userActions.js. Действия возвращают объект, тогда как функция fetchUsers () возвращает другую функцию.

export const fetchUsers = () => {
     return function(dispatch) {
       dispatch(fetchUSersRequest())
       axios.get('https://jsonplaceholder.typicode.com/users')
            .then(response => {
            const users = response.data
            dispatch(fetchUsersSuccess(users))
           })
           .catch(error => {
           const errors = error.message
           dispatch(fetchUsersFailure(errors))
           })
     }
}

Импортируйте все действия и функции в общую папку index.js.

export * from './user/userActions'

Теперь мы можем определить UserContainer.js. Здесь мы сможем получить доступ к действиям состояния и диспетчеризации с помощью props.

import React,{useEffect} from 'react'
import {connect} from 'react-redux'
import {fetchUsers} from '../redux'
function UserContainer({ userData,fetchUsers }) {
    useEffect(() => {
       fetchUsers()
    },[])
    return userData.loading ? (
      <h2>Loading</h2>
      ) : userData.error ? (
      <h2>{userData.error}</h2>
      ) : (
     <div>
       <h2>Users List</h2>
       <div>
       {
        userData && userData.users.map(user => <p>{user.name}</p>)
       }
       </div>
     </div>
    )
}
const mapStateToProps = (state) => {
   return {
     userData: state.user
   }
}
const mapDispatchToProps = (dispatch) => {
   return {
     fetchUsers: () => {dispatch(fetchUsers())}
   }
}
export default connect(mapStateToProps,mapDispatchToProps)(UserContainer)

Затем включите компонент в App.js

import React from 'react'
import {Provider} from 'react-redux'
import store from './redux/store'
import './App.css'
import CakeContainer from './components/CakeContainer'
import IceCreamContainer from './components/IceCreamContainer'
import NewCakeContainer from './components/NewCakeContainer'
import UserContainer from './components/UserContainer'
function App() {
    return (
      <Provider store={store}>
        <div className="App">
          <CakeContainer />
          <IceCreamContainer />
          <NewCakeContainer />
          <UserContainer />
        </div>
     </Provider>
    )
}
export default App

Теперь наше приложение готово. Мы шаг за шагом рассмотрели основы redux и то, как реализовать redux в React.