Создание полнофункционального приложения React Redux Express, часть II

Введение

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

Почему я должен использовать Redux?

Redux помогает вам управлять «глобальным» состоянием — состоянием, которое необходимо во многих частях вашего приложения.

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

Когда я должен использовать Redux?

Redux более полезен, когда:

  • У вас есть большое количество состояния приложения, которое необходимо во многих местах приложения.
  • Состояние приложения часто обновляется с течением времени
  • Логика обновления этого состояния может быть сложной
  • Приложение имеет кодовую базу среднего или большого размера, и над ним может работать много людей.

Шаги по разработке приложения React Redux с пониманием общей терминологии в процессе

Структура папок финального проекта будет выглядеть так:

Действия

Действие — это простой объект JavaScript с полем type. Вы можете думать о действии как о событии, описывающем что-то, что произошло в приложении.

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

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

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

Отправлять

В магазине Redux есть метод под названием dispatch. Единственный способ обновить состояние — вызвать dispatch() и передать объект действия. Хранилище запустит функцию редуктора и сохранит внутри новое значение состояния. Это приведет к отображению пользовательского интерфейса с новым значением состояния.

Редукторы

Редьюсер — это функция, которая получает текущий объект state и объект action, решает, как обновить состояние при необходимости, и возвращает новое состояние: (state, action) => newState. Вы можете думать о редюсере как о прослушивателе событий, который обрабатывает события на основе полученного типа действия (события).

Редукторы должны всегда следовать определенным правилам:

  • Они должны только вычислить новое значение состояния на основе аргументов state и action.
  • Им не разрешено изменять существующий state. Вместо этого они должны сделать неизменяемые обновления, скопировав существующие state и внеся изменения в скопированные значения.

Магазин

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

Магазин создается путем передачи редьюсера.

Давайте применим вышеизложенную логику для разработки приложения React на основе избыточности:

  1. Создайте базовое приложение на основе React, работающее одновременно с серверной частью Node Express, как описано в предыдущем блоге:


2. Обновите содержимое client/package.json, как показано ниже. Добавлены зависимости на основе Redux.

{
"name": "react-redux-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.1",
"react-scripts": "3.4.3",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:5000/",
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]}}

3. После обновления package.json запустите npm installcommand в каталоге client, чтобы установить все необходимые зависимости.

4. Добавьте файл store.js (client/src/store.js), чтобы создать хранилище для приложения React Redux.

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

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' //Middleware
import appReducer from './reducers'
const store = createStore(appReducer,applyMiddleware(thunk));
export default store;

5. После того, как хранилище создано, создайте папку с именем reducers (client/src/reducers) для хранения файлов reducers. Добавьте 2 файла в эту папку редукторов: -

  1. DashboardReducer.js (client/src/reducers/dashboardReducer.js)
export const initialState = {
dataFromBackend : {},
getDataFromBackend : {}
}
export const dashboardReducer = (state = initialState, action) => {
switch(action.type){
case 'DATA_FROM_BACKEND' :
return {
...state,
dataFromBackend : action.dataFromBackend
}
case 'GET_DATA' :
return {
...state,
getDataFromBackend : action.getDataFromBackend
}
default:
return state
}
}

2. index.js (client/src/reducers/index.js) — этот файл используется для объединения нескольких импортированных редьюсеров (например, ./dashboardReducer) в случае сложного приложения, где имеется более одного файла редьюсера. настоящее.

import {combineReducers} from 'redux'
import {dashboardReducer} from './dashboardReducer'
const config = {
dashboardReducer : dashboardReducer
}
const appReducer  = combineReducers(config);
export default appReducer;

6. После добавления редьюсеров для приложения создайте папку действий (client/src/actions), чтобы сохранить файлы создателя действий. Добавьте файл dashboardAction.js создателя действия (client/src/actions/dashboardAction.js) в эту папку.

//Async action creator for POST API Route
export const sendData = (url,payload) => {
return dispatch => {
return fetch(url,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
}).then(res => {
return res.json();
}).then(res => {
dispatch({type : 'DATA_FROM_BACKEND', dataFromBackend : res })
}).catch(err => {
console.log('API failed')
})}}
//Async action creator for GET API Route
export const getData = (url) => {
return dispatch => {
return fetch(url).then(res => {
return res.json();
}).then(res => {
dispatch({type : 'GET_DATA', getDataFromBackend : res })
}).catch(err => {
console.log('API failed')
})}}

7. Когда мы будем готовы к созданию хранилища, редуктора и асинхронного действия, давайте встроим хранилище в приложение React, обновив код файла index.js (client/src/index.js).

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
//React-Redux
import store from './store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
   <App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

8. Обновите файл App.js (client/src/App.js), чтобы компонент React был связан с хранилищем как компонент более высокого порядка с помощью библиотеки подключения.

Функция connect() соединяет компонент React с магазином Redux. Функции mapStateToProps и mapDispatchToProps относятся к state и dispatch вашего магазина Redux соответственно. state и dispatch будут переданы вашим функциям mapStateToProps или mapDispatchToProps в качестве первого аргумента.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
//Redux components
import {sendData , getData} from './actions/dashboardAction'
import {connect } from 'react-redux'
import {bindActionCreators} from 'redux'
class App extends Component {
  constructor(props){
    super();
    this.state = {
      response: '',
      post: '',
      responseToPost: '',
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.callApi = this.callApi.bind(this);
  }
  
  
  componentDidMount() {
    this.callApi();
  }
  
  callApi = () => {    
    this.props.getData('/api/hello');
  };
  
  handleSubmit = () => {
    this.props.sendData('/api/data',{ post: this.state.post });
  };
  
render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
        <p>{this.props.getDataFromBackend.express}</p>
        <div>
          <p>
            <strong>Post to Server:</strong>
          </p>
          <input
            type="text"
            value={this.state.post}
            onChange={e => this.setState({ post: e.target.value })}
          />
          <button onClick={this.handleSubmit}>Submit</button>
        </div>
        <p style={{color : 'blue'}}><b>{this.props.dataFromBackend.data}</b></p>
      </div>
    );
  }
}
//React Redux connecting code
function mapStateToProps(state){
  return {
   dataFromBackend : state.dashboardReducer.dataFromBackend,
   getDataFromBackend : state.dashboardReducer.getDataFromBackend
  }
}
const mapDispatchToProps = dispatch => bindActionCreators({
  sendData,
  getData
},dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(App);

Окончательная структура папки вашего приложения React-Redux-Express будет выглядеть так:

Запуск приложения

После внесения вышеуказанных необходимых изменений кода запустите команду npm start в корневой папке проекта (react-redux-express-app)

npm start

Это запустит приложение React и одновременно запустит сервер.

Теперь перейдите к http://localhost:3000, и вы попадете в приложение React Redux, отображающее сообщение, пришедшее с нашего экспресс-маршрута GET.

Полный исходный код можно найти в Github Repository.

Вывод

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

Предыдущий пост: Создание полного стека приложения React Redux Express, часть I

Спасибо за чтение. Удачного кодирования!