React - использование Redux в приложении React

Внимание

Данный материал является частью цикла статей «Создаем приложение на React с использованием Redux». Не забудьте посмотреть другие статьи по этой теме :-)

  1. React - Настройка проекта и подготовка компонентов
  2. React - навигация и маршрутизация
  3. React - использование Redux в приложении React
  4. React – Lazy Loading и компоненты высшего порядка (HOC)
  5. React - обработка исключений
  6. React - создание динамических форм и модальных компонентов
  7. React - валидация формы и обработка POST запроса
  8. React - обработка запроса PUT для редактирования данных
  9. React - обработка запроса на удаление данных

Создание экземпляра Axios

Для установки axios выполните эту команду:

npm install --save axios

Хотя мы могли бы использовать собственный экземпляр axios (тот, который мы только что установили) для отправки HTTP-запросов, лучший способ - создать наш собственный экземпляр. В этом настраиваемом экземпляре мы можем определить базовое свойство url и назначить заголовки и другие полезные элементы. Если нам нужно больше экземпляров аксиом, мы также можем их создать. Итак, давайте создадим наш собственный экземпляр axios.

Внутри папки src создайте новую папку и назовите ее axios. Внутри этой папки создайте новый файл и назовите его axios.js. Теперь давайте изменим файл axios.js:

import axios from 'axios';

const instance = axios.create({
    baseURL: 'http://localhost:5000',
    headers: {
        headerType: 'example header type'
    }
});

export default instance;

В этом примере кода мы импортируем библиотеку axios, а затем создаем новый экземпляр с дополнительными свойствами. Это очень полезно, потому что нам больше не нужно писать полные абсолютные url-адреса (axios.get(http://locahost:5000/api/owner)) для наших HTTP-запросов. Теперь мы можем использовать относительные пути (axios.get(/api/owner)), потому что мы используем наш собственный экземпляр axios с предопределенным свойством baseURL.

О Redux в приложении React

Redux - это контейнер состояний для приложений JavaScript. Хотя вначале это кажется немного сложным, с небольшой практикой вы поймете, что Redux совсем не такой сложный. И это очень помогает. Redux не является библиотекой React, он совместим с любым другим фреймворком JavaScript, но хорошо работает с React.

Чтобы установить Redux, выполните эту команду:

npm install --save redux

Одного Redux будет недостаточно. Нам нужно создать связь между React и Redux, и для этого давайте установим библиотеку react-redux:

npm install --save react-redux

Теперь у нас есть обе библиотеки, необходимые для работы нашего приложения с Redux. Итак, мы можем перейти к реализации Redux. Но прежде чем мы продолжим, давайте взглянем на эту диаграмму, которая подробно объясняет, как работает Redux (часть компонентов является отправной точкой):

Redux - Типы действий

Начнем с создания следующей структуры внутри папки src:

В папке actions создайте новый файл и назовите его actionTypes.js. Теперь давайте изменим этот файл:

export const GET_DATA_SUCCESS = 'GET_DATA_SUCCESS';
export const POST_DATA_SUCCESS = 'POST_DATA_SUCCESS';
export const PUT_DATA_SUCCESS = 'PUT_DATA_SUCCESS';
export const DELETE_DATA_SUCCESS = 'DELETE_DATA_SUCCESS';

Наш файл редьюсер (который мы собираемся создать позже в этом посте) будет использовать эти типы действий для переключения между различными способами обновления состояния.

Redux - Действия (actions)

Нам нужно создать новый файл в папке actions и назвать его repositoryActions.js. Мы собираемся обрабатывать HTTP-запросы async внутри этого файла и возвращать объект, который файл-редьюсер будет использовать для обновления состояния. Каждый возвращаемый объект должен иметь хотя бы одно свойство с именем «тип». Значение свойства type будет одним из actionTypes из файла actionType.js (созданного ранее).

Теперь давайте добавим операторы импорта для actionTypes и нашего экземпляра axios в файл repositoryActions.js:

import * as actionTypes from './actionTypes';
import axios from '../../axios/axios';

Затем нам нужно добавить две функции. Один для обработки запроса GET, а второй для возврата объекта со свойством type и данными с сервера:

const getDataSuccess = (data) => {
    return {
        type: actionTypes.GET_DATA_SUCCESS,
        data: data
    }
}

export const getData = (url, props) => {
    return (dispatch) => {
        axios.get(url)
        .then(response => {
            dispatch(getDataSuccess(response.data));
        })
        .catch(error => {
            //TODO: код обработчика при возникновении ошибки
        })
    }
}

Мы экспортируем функцию getData. Эта функция будет вызываться из нашего компонента для получения данных с сервера (поэтому мы должны экспортировать их из этого файла). Затем с помощью axios мы отправляем GET-запрос. В случае успеха мы отправляем функцию getDataSuccess, которая возвращает объект для использования в файле reducer. Этот объект имеет обязательное свойство type, а также свойство data, полученное с сервера.

Ниже функции getData давайте реализуем все остальные функции по тому же шаблону:

const postDataSuccess = (response) => {
    return {
        type: actionTypes.POST_DATA_SUCCESS,
        response: response
    }
}

export const postData = (url, obj, props) => {
    return (dispatch) => {
        axios.post(url, obj)
        .then(response => {
            dispatch(postDataSuccess(response));
        })
        .catch(error => {
            //TODO: код обработчика при возникновении ошибки
        })
    }
}

const putDataSuccess = (response) => {
    return {
        type: actionTypes.PUT_DATA_SUCCESS,
        response: response
    }
}

export const putData = (url, obj, props) => {
    return (dispatch) => {
        axios.put(url, obj)
        .then(response => {
            dispatch(putDataSuccess(response));
        })
        .catch(error => {
            //TODO: код обработчика при возникновении ошибки
        })
    }
}

const deleteDataSuccess = (response) => {
    return {
        type: actionTypes.DELETE_DATA_SUCCESS,
        response: response
    }
}

export const deleteData = (url, props) => {
    return (dispatch) => {
        axios.delete(url)
        .then(response => {
            dispatch(deleteDataSuccess(response));
        })
        .catch(error => {
            //TODO: код обработчика при возникновении ошибки
        })
    }
}

Вот и все, теперь у нас есть реализация файла repositoryActions.js, и пришло время создать и реализовать файл reducer.

React - Reducer

Давайте создадим новый файл внутри папки reducer и назовем его repositoryReducer.js:

В этом файле мы собираемся проверить свойство type, которое мы возвращаем из файла repositoryActions.js. Затем, основываясь на значении свойства type, мы собираемся обновить наше состояние.

Итак, давайте изменим файл repositoryActions.js:

import * as actionTypes from '../actions/actionTypes';

const initialState = {
    data: null,
    showSuccessModal: false
}

Мы импортируем actionTypes и создаем состояние с именем initialState. Свойство data будет хранить данные с сервера, а свойство showSuccessModal служит для отображения или скрытия модального окна успеха, когда действие POST, PUT или DELETE выполнено успешно.

Теперь давайте создадим функцию-редьюсер под нашим объектом состояния:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.GET_DATA_SUCCESS:
            return executeGetDataSuccess(state, action);
        case actionTypes.POST_DATA_SUCCESS:
            return executePostDataSuccess(state, action);
        case actionTypes.PUT_DATA_SUCCESS:
            return executePutDataSuccess(state, action);
        case actionTypes.DELETE_DATA_SUCCESS:
            return executeDeleteDataSuccess(state, action);
        default:
            return state;
    }
}

export default reducer;

Эта функция reducer принимает два параметра: состояние, которое мы инициализируем своим начальным состоянием, и действие. Мы собираемся использовать этот параметр state для обновления нашего initialState и параметра action, чтобы сохранить объект (со свойством type), отправленный из файла repositoryActions.js. Таким образом, всякий раз, когда мы отправляем какое-либо действие (которое возвращает объект со свойством type и всеми другими свойствами) из файла repositoryActions.js, эта функция reducer-а запускается и принимает отправленный объект внутри action. Как следствие, редьюсер будет переключаться между типами действий и выполнять соответствующую функцию.

Наконец, давайте добавим соответствующие функции прямо над нашей функцией reducer:

const executeGetDataSuccess = (state, action) => {
    return {
        ...state,
        data: action.data
    }
}

const executePostDataSuccess = (state, action) => {
    return {
        ...state,
        showSuccessModal: true
    }
}

const executePutDataSuccess = (state, action) => {
    return {
        ...state,
        showSuccessModal: true
    }
}

const executeDeleteDataSuccess = (state, action) => {
    return {
        ...state,
        showSuccessModal: true
    }
}

Эти функции обновляют наше состояние. Во-первых, мы глубоко клонируем наш объект состояния, используя оператор распространения (…), а затем просто переопределяем свойство, которое хотим обновить в нашем объекте состояния. Поскольку объекты и массивы являются ссылочными типами, нам необходимо выполнить их глубокое клонирование до любых изменений. Таким образом мы постоянно обновляем состояние.

Чтобы это состояние было доступно в любом компоненте-владельце, нам необходимо зарегистрировать этот редьюсер в файле index.js.

Регистрация файла редьюсера

Перед тем, как зарегистрировать наш файл-редьюсер, мы должны установить еще одну стороннюю библиотеку с именем thunk:

npm install --save redux-thunk

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

Теперь мы можем зарегистрировать наш редьюсер:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './containers/App';
import registerServiceWorker from './registerServiceWorker';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
import repositoryReducer from './store/reducers/repositoryReducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(repositoryReducer, applyMiddleware(thunk));

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
registerServiceWorker();

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

Вот и все, мы подготовили настройку redux для нашего проекта. Самое приятное в этом то, что мы можем использовать его для любого компонента внутри нашего проекта, и если у нас есть компоненты, которые требуют более сложной или другой настройки редьюсера, все, что нам нужно сделать, это создать другой файл действия и редьюсера и зарегистрировать его. внутри файла index.js. В одном из следующих постов мы покажем вам, как этого добиться, объединив редьюсеры внутри файла index.js.

Заключение

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

Прочитав этот пост, вы узнали:

  • Способ настройки нового экземпляра Axios
  • Как установить Redux и React-Redux
  • О типах действий, контейнерах действий и редьюсерах
  • Как зарегистрировать файл редьюсера

Спасибо, что прочитали статью, и я надеюсь, что вы нашли в ней что-то полезное.

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