Рубрики
Без рубрики

Как загрузить данные в реакции с redux-thunk, redux-saga, suspense & hooks

Valerii Tereshenko Как загрузить данные в реакции с redux-thunk, redux-saga, suspense & coundsintroductionreact – это библиотека JavaScript для построения пользовательских интерфейсов. Очень часто используют реагирование означает использование реагирования с redux. Redux – еще одна библиотека JavaScript для управления глобальным состоянием. К сожалению, даже с этими двумя библиотеками нет

Автор оригинала: FreeCodeCamp Community Member.

Валерий Терещенко

Вступление

Реагировать Является ли библиотека JavaScript для построения пользовательских интерфейсов. Очень часто используют реагирование означает использование реагирования с Redux Отказ Redux это еще одна библиотека JavaScript для управления глобальным состоянием. К сожалению, даже с этими двумя библиотеками нет никого, как обрабатывать асинхронные вызовы на API (Backend) или любые другие побочные эффекты.

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

Компонент X – один из многих компонентов веб-сайта (или мобильного или настольного приложения, также возможно). X queries и показывает некоторые данные, загруженные из API. X может быть страницей или просто часть страницы. Важно, что X – это отдельный компонент, который должен быть свободно связан с остальной частью системы (как можно больше). X следует показать индикатор загрузки, когда данные оказываются и ошибка, если вызов не удается.

Эта статья предполагает, что у вас уже есть опыт создания приложений React/Redux.

Эта статья собирается показать 4 способа решения этой проблемы и Сравните плюсы и минусы каждого. Это не детальное руководство о том, как использовать Thunk, Saga, Suspatee или крючки Отказ

Код этих примеров доступен на Github Отказ

Начальная настройка

Mock Server

Для целей тестирования мы собираемся использовать JSON-Server Отказ Это удивительный проект, который позволяет вам построить поддельные apit отдыха очень быстро. Для нашего примера это выглядит так.

const jsonServer = require('json-server');const server = jsonServer.create();const router = jsonServer.router('db.json');const middleware = jsonServer.defaults();
server.use((req, res, next) => {   setTimeout(() => next(), 2000);});server.use(middleware);server.use(router);server.listen(4000, () => {   console.log(`JSON Server is running...`);});

Наш файл db.json содержит тестовые данные в формате json.

{ "users": [   {     "id": 1,     "firstName": "John",     "lastName": "Doe",     "active": true,     "posts": 10,     "messages": 50   },   ...   {     "id": 8,     "firstName": "Clay",     "lastName": "Chung",     "active": true,     "posts": 8,     "messages": 5   } ]}

После запуска сервера звонок к http://localhost: 4000/пользователи Возвращает список пользователей имитацией задержки – около 2с.

Проект и API звонок

Теперь мы готовы начать кодирование. Я предполагаю, что у вас уже есть реагированный проект, созданный с использованием Create-React-App с redux настроен и готов к использованию.

Если у вас есть какие-либо трудности с этим, вы можете проверить это и это Отказ

Следующим шагом является создание функции для вызова API ( API.JS ):

const API_BASE_ADDRESS = 'http://localhost:4000';
export default class Api {   static getUsers() {       const uri = API_BASE_ADDRESS + "/users";
       return fetch(uri, {           method: 'GET'       });   }}

Redux-thunk.

Redux-Thunk Рекомендуемое промежуточное программное обеспечение для базовой логики побочных эффектов Redux, таких как простая асинхронная логика (например, запрос на API). Сам redux-thunk не много не делают. Это просто 14 !!! линии Код Отказ Это просто добавляет какой-то «синтаксический сахар» и ничего более.

Блок-схема ниже помогает понять, что мы собираемся сделать.

Каждый раз, когда выполняется действие, редуктор меняет состояние соответственно. Компонент отображает состояние для свойств и использует эти свойства в Реддер () Способ понять, что пользователь должен видеть: индикатор загрузки, данные или сообщение об ошибке.

Чтобы сделать это работой, нам нужно сделать 5 вещей.

1. Установите Tunk

npm install redux-thunk

2. Добавить промежуточное программное обеспечение Thunk при настройке магазина (ConfigureStore.js)

import { applyMiddleware, compose, createStore } from 'redux';import thunk from 'redux-thunk';import rootReducer from './appReducers';
export function configureStore(initialState) { const middleware = [thunk];
 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(...middleware)));
 return store;}

В строках 12-13 мы также настраиваем redux devtools Отказ Чуть позже это поможет показать одну из проблем с этим решением.

3. Создание действий (Redux-Thunk/Actions.js)

import Api from "../api"
export const LOAD_USERS_LOADING = 'REDUX_THUNK_LOAD_USERS_LOADING';export const LOAD_USERS_SUCCESS = 'REDUX_THUNK_LOAD_USERS_SUCCESS';export const LOAD_USERS_ERROR = 'REDUX_THUNK_LOAD_USERS_ERROR';
export const loadUsers = () => dispatch => {   dispatch({ type: LOAD_USERS_LOADING });
   Api.getUsers()       .then(response => response.json())       .then(           data => dispatch({ type: LOAD_USERS_SUCCESS, data }),           error => dispatch({ type: LOAD_USERS_ERROR, error: error.message || 'Unexpected Error!!!' })       )};

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

4. Создать редуктор (Redux-Thunk/Reeder.js)

import {LOAD_USERS_ERROR, LOAD_USERS_LOADING, LOAD_USERS_SUCCESS} from "./actions";
const initialState = {   data: [],   loading: false,   error: ''};
export default function reduxThunkReducer(state = initialState, action) {   switch (action.type) {       case LOAD_USERS_LOADING: {           return {               ...state,               loading: true,               error:''           };       }       case LOAD_USERS_SUCCESS: {           return {               ...state,               data: action.data,               loading: false           }       }       case LOAD_USERS_ERROR: {           return {               ...state,               loading: false,               error: action.error           };       }       default: {           return state;       }   }}

5. Создайте компонент, подключенный к redux (Redux-Thunk/пользователиWithreduxuskth.js)

import * as React from 'react';import { connect } from 'react-redux';import {loadUsers} from "./actions";
class UsersWithReduxThunk extends React.Component {   componentDidMount() {       this.props.loadUsers();   };
   render() {       if (this.props.loading) {           return 
Loading
}
       if (this.props.error) {           return 
ERROR: {this.props.error}
}
       return (                          {this.props.data.map(u =>                                          <;td>{u.posts}                                      )}               
First Name Last Name ;Active? Posts Messages
{u.firstName} {u.lastName} {u.active ? 'Yes' : 'No'}{u.messages}
); }}
const mapStateToProps = state => ({   data: state.reduxThunk.data,   loading: state.reduxThunk.loading,   error: state.reduxThunk.error,});
const mapDispatchToProps = {   loadUsers};
export default connect(   mapStateToProps,   mapDispatchToProps)(UsersWithReduxThunk);

Я пытался сделать компонент как можно просто. Я понимаю, что это выглядит ужасно:)

Индикатор загрузки

Данные

Ошибка

Там у вас есть: 3 файлы, 109 строк кода (13 (действия) + 36 (редуктор) + 60 (компонент)).

Плюсы:

  • «Рекомендуемый» подход для приложений React/Redux.
  • Нет дополнительных зависимостей. Почти, Thunk Tiny:)
  • Нет необходимости изучать новые вещи.

Минусы:

  • Много кода в разных местах
  • После навигации на другую страницу старые данные все еще в глобальном состоянии (см. Рисунок ниже). Эти данные устарели и бесполезная информация, которая потребляет память.
  • В случае сложных сценариев (несколько условных вызовов в одном действии и т. Д.) Код не очень читается

Redux-saga

Redux-Saga Это библиотека промежуточного программного обеспечения redux, предназначенная для обработки побочных эффектов легким и читаемым. Он использует генераторы ES6, которые позволяют нам писать асинхронный код, который выглядит синхронно. Кроме того, это решение легко проверить.

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

Чтобы сделать это работать, нам нужно сделать 6 вещей.

1. Установите сагу

npm install redux-saga

2. Добавить Saga Mardware и добавить все саги (ConfigureStore.js)

import { applyMiddleware, compose, createStore } from 'redux';import createSagaMiddleware from 'redux-saga';import rootReducer from './appReducers';import usersSaga from "../redux-saga/sagas";
const sagaMiddleware = createSagaMiddleware();
export function configureStore(initialState) { const middleware = [sagaMiddleware];
 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(...middleware)));
 sagaMiddleware.run(usersSaga);
 return store;}

Саги из строки 4 будут добавлены на шаге 4.

3. Создать действие (Redux-Saga/Actions.js)

export const LOAD_USERS_LOADING = 'REDUX_SAGA_LOAD_USERS_LOADING';export const LOAD_USERS_SUCCESS = 'REDUX_SAGA_LOAD_USERS_SUCCESS';export const LOAD_USERS_ERROR = 'REDUX_SAGA_LOAD_USERS_ERROR';
export const loadUsers = () => dispatch => {   dispatch({ type: LOAD_USERS_LOADING });};

4. Создание сага (redux-saga/sagas.js)

import { put, takeEvery, takeLatest } from 'redux-saga/effects'import {loadUsersSuccess, LOAD_USERS_ERROR, LOAD_USERS_LOADING, LOAD_USERS_SUCCESS} from "./actions";import Api from '../api'
async function fetchAsync(func) {   const response = await func();
   if (response.ok) {       return await response.json();   }
   throw new Error("Unexpected error!!!");}
function* fetchUser() {   try {       const users = yield fetchAsync(Api.getUsers);
       yield put({type: LOAD_USERS_SUCCESS, data: users});   } catch (e) {       yield put({type: LOAD_USERS_ERROR, error: e.message});   }}
export function* usersSaga() {   // Allows concurrent fetches of users   yield takeEvery(LOAD_USERS_LOADING, fetchUser);
   // Does not allow concurrent fetches of users   // yield takeLatest(LOAD_USERS_LOADING, fetchUser);}
export default usersSaga;

SAGA имеет довольно крутущую кривую обучения, поэтому, если вы никогда не использовали его, и никогда ничего не использовали об этой структуре, это может быть трудно понять, что происходит здесь. Вкратце, в usersaga Функция Мы настраиваем SAGA для прослушивания Load_users_loading Действие и триггер fetchusers функция. fetchusers Функция вызывает API. Если вызов преуспевает, то Load_user_success Действие отправляется, в противном случае Load_user_Error Действие отправляется.

5. Создать редуктор (redux-saga/redeber.js)

import {LOAD_USERS_ERROR, LOAD_USERS_LOADING, LOAD_USERS_SUCCESS} from "./actions";
const initialState = {   data: [],   loading: false,   error: ''};
export default function reduxSagaReducer(state = initialState, action) {   switch (action.type) {       case LOAD_USERS_LOADING: {           return {               ...state,               loading: true,               error:''           };       }       case LOAD_USERS_SUCCESS: {           return {               ...state,               data: action.data,               loading: false           }       }       case LOAD_USERS_ERROR: {           return {               ...state,               loading: false,               error: action.error           };       }       default: {           return state;       }   }}

Редуктор здесь абсолютно такой же, как в примере Thunk.

6. Создать компонент, подключенный к redux (redux-saga/userswithreduxaga.js)

import * as React from 'react';import {connect} from 'react-redux';import {loadUsers} from "./actions";
class UsersWithReduxSaga extends React.Component {   componentDidMount() {       this.props.loadUsers();   };
   render() {       if (this.props.loading) {           return 
Loading
}
       if (this.props.error) {           return 
ERROR: {this.props.error}
}
       return (           ;                                  {this.props.data.map(u =>                                          )}               
First Name Last Name ;Active? Posts Messages
{u.firstName} ;{u.lastName} {u.active ? 'Yes' : 'No'} {u.posts} {u.messages}
); }}
const mapStateToProps = state => ({   data: state.reduxSaga.data,   loading: state.reduxSaga.loading,   error: state.reduxSaga.error,});
const mapDispatchToProps = {   loadUsers};
export default connect(   mapStateToProps,   mapDispatchToProps)(UsersWithReduxSaga);

Компонент также почти такой же, как в примере Thunk.

Так что здесь у нас есть 4 файла, 136 строк кода (7 (Действия) + 36 (редуктор) + саги (33) + 60 (компонент)).

Плюсы:

  • Более читаемый код (Async/a enae)
  • Хорошо для обработки сложных сценариев (несколько условных вызовов в одном действии, действие может иметь несколько слушателей, отмена действий и т. Д.)
  • Легко установить тест

Минусы:

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

Преодоленность

Взять – это новая особенность в реакции 16.6.0. Это позволяет нам отложить рендеринг часть компонента, пока не будет выполнено некоторое условие (например, данные из загруженного API).

Чтобы сделать это работой, нам нужно сделать 4 вещи (это определенно становится лучше:)).

1. Создайте кеш (SOUSSENT/CACHE.JS)

Для кеша мы собираемся использовать Простой-кэш-провайдер который является основным провайдером кэша для приложений ADCT.

import {createCache} from 'simple-cache-provider';
export let cache;
function initCache() { cache = createCache(initCache);}
initCache();

2. Создать границу ошибки (SOUSSENS/ERRORBOODY.JS)

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

import React from 'react';
export class ErrorBoundary extends React.Component { state = {};
 componentDidCatch(error) {   this.setState({ error: error.message || "Unexpected error" }); }
 render() {   if (this.state.error) {     return 
ERROR: {this.state.error || 'Unexpected Error'}
; }
   return this.props.children; }}
export default ErrorBoundary;

3. Создать таблицу пользователей (SoSsene/Userstable.js)

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

import * as React from 'react';import {createResource} from "simple-cache-provider";import {cache} from "./cache";import Api from "../api";
let UsersResource = createResource(async () => {   const response = await Api.getUsers();   const json = await response.json();
   return json;});
class UsersTable extends React.Component {   render() {       let users = UsersResource.read(cache);
       return (                          {users.map(u =>                                          <;td>{u.posts}                                      )}               
First Name ;Last Name Active? Posts Messages
{u.firstName} {u.lastName} {u.active ? 'Yes' : 'No'}{u.messages}
); }}
export default UsersTable;

4. Создать компонент (SOUSSENSE/USERSSWITHSUSPENSE.JS)

import * as React from 'react';import UsersTable from "./UsersTable";import ErrorBoundary from "./ErrorBoundary";
class UsersWithSuspense extends React.Component {   render() {       return (                          Loading
}> ); }}
export default UsersWithSuspense;

4 файла, 106 строки кода (9 (кэш) + 19 (CREARBOUNDARY) + USERSTABLE (33) + 45 (компонент)).

3 файлы, 87 строки кода (9 (кэш) + USERSTABLABLE (33) + 45 (компонент)) Если мы предположим, что Errorboundary является многоразовым компонентом.

Плюсы:

  • Нет необходимости Redux. Этот подход можно использовать без Redux. Компонент полностью независима.
  • Нет дополнительных зависимостей ( просто-кэш-провайдер является частью реагирования)
  • Задержка отображения индикатора загрузки по установке свойства Dellayms
  • Меньше строк кода, чем в предыдущих примерах

Минусы:

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

Крючки

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

Чтобы сделать его работать на наш пример, нам нужно сделать Один (!) вещь:

1. Создание и используйте крючки (крючки/пользователиWithhooks.js)

Здесь мы создаем 3 крючка (функции) для «крючка» в состоянии «Реагистрационное состояние».

import React, {useState, useEffect} from 'react';import Api from "../api";
function UsersWithHooks() {   const [data, setData] = useState([]);   const [loading, setLoading] = useState(true);   const [error, setError] = useState('');
   useEffect(async () => {       try {           const response = await Api.getUsers();           const json = await response.json();
           setData(json);       } catch (e) {           setError(e.message || 'Unexpected error');       }
       setLoading(false);   }, []);
   if (loading) {       return 
Loading
}
   if (error) {       return 
ERROR: {error}
}
   return (       ;                          {data.map(u =>               ;                   ;                              )}           
First Name Last Name Active? Posts Messages
;{u.firstName}{u.lastName} {u.active ? 'Yes' : 'No'}<;/td> {u.posts} {u.messages}
);}
export default UsersWithHooks;

И вот это – всего 1 файл, 56 строки кода !!!

Плюсы:

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

Минусы:

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

Заключение

Давайте сначала организуем эти метрики в качестве стола.

  • Redux все еще является хорошим вариантом для управления глобальным состоянием (если у вас есть)
  • Каждый вариант имеет плюсы и минусы. Какой подход лучше зависит от проекта: его сложность, используют случаи, знания команды, когда проект собирается на производство и т. Д.
  • Сага может помочь со сложными случаями использования
  • Ожидание и крючки, которые стоит рассмотреть (или, по крайней мере, обучение), особенно для новых проектов

Вот и все – наслаждайтесь и счастливым кодированием!