Автор оригинала: 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) { returnLoading}
if (this.props.error) { returnERROR: {this.props.error}}
return (
First Name | Last Name | ;Active? | Posts | Messages |
---|---|---|---|---|
{u.firstName} | {u.lastName} | {u.active ? 'Yes' : 'No'} | <;td>{u.posts}{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) { returnLoading}
if (this.props.error) { returnERROR: {this.props.error}}
return (
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) { returnERROR: {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 (
First Name | ;Last Name | Active? | Posts | Messages |
---|---|---|---|---|
{u.firstName} | {u.lastName} | {u.active ? 'Yes' : 'No'} | <;td>{u.posts}{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) { returnLoading}
if (error) { returnERROR: {error}}
return (
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 все еще является хорошим вариантом для управления глобальным состоянием (если у вас есть)
- Каждый вариант имеет плюсы и минусы. Какой подход лучше зависит от проекта: его сложность, используют случаи, знания команды, когда проект собирается на производство и т. Д.
- Сага может помочь со сложными случаями использования
- Ожидание и крючки, которые стоит рассмотреть (или, по крайней мере, обучение), особенно для новых проектов
Вот и все – наслаждайтесь и счастливым кодированием!