在React中加载数据:redux-thunk,redux-saga,suspense,hooks

目录

介绍

初始设置

模拟服务器

项目和API调用

Redux-thunk

Redux-saga 

Suspense

Hooks

结论


介绍

React 是一个用于构建用户界面的JavaScript库。经常使用React意味着将ReactRedux一起使用Redux 是另一个用于管理全局状态的JavaScript库。遗憾的是,即使有了这两个库,也没有一种明确的方法来处理对API(后端)的异步调用或任何其他的副作用。

在本文中,我试图比较解决此问题的不同方法。我们先来定义问题。

组件X是网站的许多组件之一(或移动或桌面应用程序,它也是可能的)。X查询并显示从API加载的一些数据。X可以是页面,也可以只是页面的一部分。重要的是X是一个独立的组件,应该与系统的其余部分松散地耦合(尽可能多)。X应该在数据检索时显示加载指示符,如果调用失败则显示错误。

本文假设您已经具备了创建React / Redux应用程序的一些经验。

本文将展示解决这个问题的4种方法,并比较每种方法的优缺点没有详细的使用说明如何使用thunksagasuspencehooks

GitHub提供了这些示例的代码。

初始设置

模拟服务器

出于测试目的,我们将使用json server。这个令人惊叹的项目允许非常快速地构建虚假的REST API。对于我们的示例,它看起来像这样。

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/users返回模仿延迟大约2s的用户列表。

项目和API调用

现在我们准备开始编码了。我假设您已经使用create react app 创建了 React项目,并配置了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代码。它只是添加了一些语法糖,仅此而已。

下面的流程图有助于了解我们将要做什么。

https://www.codeproject.com/KB/scripting/1275809/8cbd7ea4-f87f-4a52-be4d-e5c16895d53c.Png

每次执行动作时,减速器都会相应地改变状态。组件将状态映射到属性,并在revder()方法中使用这些属性来确定用户应该看到的内容:加载指示符,数据或错误消息。

为了使它工作,我们需要做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.创建reduserredux-thunk / reducer.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 / UsersWithReduxThunk.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 <div>Loading</div>
       }

       if (this.props.error) {
           return <div style={{ color: 'red' }}>ERROR: {this.props.error}</div>
       }

       return (
           <table>
               <thead>
                   <tr>
                       <th>First Name</th>
                       <th>Last Name</th>
                       <th>Active?</th>
                       <th>Posts</th>
                       <th>Messages</th>
                   </tr>
               </thead>
               <tbody>
               {this.props.data.map(u =>
                   <tr key={u.id}>
                       <td>{u.firstName}</td>
                       <td>{u.lastName}</td>
                       <td>{u.active ? 'Yes' : 'No'}</td>
                       <td>{u.posts}</td>
                       <td>{u.messages}</td>
                   </tr>
               )}
               </tbody>
           </table>
       );
   }
}

const mapStateToProps = state => ({
   data: state.reduxThunk.data,
   loading: state.reduxThunk.loading,
   error: state.reduxThunk.error,
});

const mapDispatchToProps = {
   loadUsers
};

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

我试图使组件尽可能简单。我明白它看起来很糟糕:)

加载指标

https://i-blog.csdnimg.cn/blog_migrate/4bea51d5d77b719803e26bfac06c4bac.png

数据

https://i-blog.csdnimg.cn/blog_migrate/d735853d6233d571fd8fdcd1efba22f6.png

错误

https://i-blog.csdnimg.cn/blog_migrate/bc807adad4a6e8045a3f9cbb006f7426.png

3个文件,109行代码(13(动作)+ 36(减速器)+ 60(组件))。

优点:

  • react/redux 应用程序的推荐方法。
  • 没有其他依赖项。差不多,thunk很小:)
  • 无需学习新事物。

缺点:

  • 很多代码在不同的地方
  • 导航到另一页后,旧数据仍处于全局状态(见下图)。这些数据已经过时,而且消耗内存的无用信息也是如此。
  • 在复杂场景(一个动作中的多个条件调用等)的情况下,代码不是非常易读 

https://i-blog.csdnimg.cn/blog_migrate/4fd623562e0a7f970fc1d33b5d909c38.png

Redux-saga 

Redux saga是一个redux中间件库,旨在以简单易读的方式处理副作用。它利用ES6 生成器,允许编写看起来同步的异步代码。此外,该解决方案易于测试。

从高层次的角度来看,这个解决方案与thunk一样。来自thunk示例的流程图仍然适用。

为了使它工作,我们需要做6件事。

1.安装saga

npm install redux-thunk

2.添加saga中间件并添加所有sagasconfigureStore.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行的Sagas将在第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.创造sagasredux-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.创建reducerredux-saga / reducer.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;
       }
   }
}

Reducerthunk示例完全相同。

6.创建连接到redux的组件(redux-saga / UsersWithReduxSaga.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 <div>Loading</div>
       }

       if (this.props.error) {
           return <div style={{color: 'red'}}>ERROR: {this.props.error}</div>
       }

       return (
           <table>
               <thead>
                   <tr>
                       <th>First Name</th>
                       <th>Last Name</th>
                       <th>Active?</th>
                       <th>Posts</th>
                       <th>Messages</th>
                   </tr>
               </thead>
               <tbody>
                   {this.props.data.map(u =>
                       <tr key={u.id}>
                           <td>{u.firstName}</td>
                           <td>{u.lastName}</td>
                           <td>{u.active ? 'Yes' : 'No'}</td>
                           <td>{u.posts}</td>
                           <td>{u.messages}</td>
                       </tr>
                   )}
               </tbody>
           </table>
       );
   }
}

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(减速器)+sagas33+ 60(组件))。

优点:

  • 更易读的代码(async/await
  • 适合处理复杂场景(一个动作中有多个条件调用,动作可以有多个侦听器,取消动作等)
  • 易于单元测试

缺点:

  • 很多代码在不同的地方
  • 导航到另一个页面后,旧数据仍处于全局状态。这些数据已经过时,而且消耗内存的无用信息也是如此。
  • 额外的依赖
  • 要学习很多概念

Suspense

SuspenseReact 16.6.0中的新功能。它允许延迟渲染组件的一部分,直到满足某些条件(例如来自API加载的数据)。

为了使它工作,我们需要做4件事(它肯定会变得更好:))。

1.创建缓存(suspense / cache.js

对于缓存,我们将使用simple - cache - provider,它是react应用程序的基本疼痛提供程序。

import {createCache} from 'simple-cache-provider';

export let cache;

function initCache() {
 cache = createCache(initCache);
}

initCache();

2.创建错误边界(suspense / ErrorBoundary.js

捕获Suspense引发的错误是一个错误边界。

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 <div style={{ color: 'red' }}>ERROR: {this.state.error || 'Unexpected Error'}</div>;
   }

   return this.props.children;
 }
}

export default ErrorBoundary;

3.创建用户表(suspense / 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 (
           <table>
               <thead>
               <tr>
                   <th>First Name</th>
                   <th>Last Name</th>
                   <th>Active?</th>
                   <th>Posts</th>
                   <th>Messages</th>
               </tr>
               </thead>
               <tbody>
               {users.map(u =>
                   <tr key={u.id}>
                       <td>{u.firstName}</td>
                       <td>{u.lastName}</td>
                       <td>{u.active ? 'Yes' : 'No'}</td>
                       <td>{u.posts}</td>
                       <td>{u.messages}</td>
                   </tr>
               )}
               </tbody>
           </table>
       );
   }
}

export default UsersTable;

4.创建组件(suspense / UsersWithSuspense.js

import * as React from 'react';
import UsersTable from "./UsersTable";
import ErrorBoundary from "./ErrorBoundary";

class UsersWithSuspense extends React.Component {
   render() {
       return (
           <ErrorBoundary>
               <React.Suspense fallback={<div>Loading</div>}>
                   <UsersTable/>
               </React.Suspense>
           </ErrorBoundary>
       );
   }
}

export default UsersWithSuspense;

4个文件,106行代码(9(缓存)+ 19(错误边界)+ 用户表(33+ 45(组件))。

3个文件,87行代码(9(缓存)+用户表(33+ 45(组件))如果我们假设错误边界是一个可重用的组件。

优点:

  • 不需要redux。这种方法可以在没有redux的情况下使用。组件完全独立。
  • 没有其他依赖项(simple-cache-provider React的一部分)
  • 通过设置dellayMs属性延迟显示加载指示器
  • 与前面的示例相比,代码更少

缺点:

  • 即使我们不需要缓存,缓存也是需要的。
  • 需要学习一些新概念(这是React的一部分)。

Hooks

到撰写本文时,hooks尚未正式发布,仅在下一个版本中可用。Hooks无疑是即将推出的最具革命性的功能之一,在最近的将来,它会在React世界中发生很大的变化。关于hooks的更多细节在这里这里

为了使它适用于我们的例子,我们需要做一件!!!!!!! 事情

1创建并使用hookshooks / UsersWithHooks.js

这里我们创建了3hooks(函数)来挂钩”React状态。

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 <div>Loading</div>
   }

   if (error) {
       return <div style={{color: 'red'}}>ERROR: {error}</div>
   }

   return (
       <table>
           <thead>
           <tr>
               <th>First Name</th>
               <th>Last Name</th>
               <th>Active?</th>
               <th>Posts</th>
               <th>Messages</th>
           </tr>
           </thead>
           <tbody>
           {data.map(u =>
               <tr key={u.id}>
                   <td>{u.firstName}</td>
                   <td>{u.lastName}</td>
                   <td>{u.active ? 'Yes' : 'No'}</td>
                   <td>{u.posts}</td>
                   <td>{u.messages}</td>
               </tr>
           )}
           </tbody>
       </table>
   );
}

export default UsersWithHooks;

1个文件,56行代码!!!!

优点:

  • 不需要redux。这种方法可以在没有redux的情况下使用。组件完全独立。
  • 没有其他依赖项
  • 代码比其他解决方案少2

缺点:

  • 对于第一次看代码,它看起来很奇怪,难以阅读和理解。习惯hooks需要一些时间。
  • 需要学习一些新概念(这是React的一部分)
  • 尚未正式发布

结论

我们先将指标组织成一个表格。

 

文件

代码行

依赖

Redux需要吗?

Thunk

3

109

0.001

yes

Saga

4

136

1

yes

Suspense

4/3

106/87

0

no

Hooks

1

56

0

no

 

  • Redux仍然是管理全局状态的好选择(如果你有的话)
  • 每个选项都有利弊。哪种方法更好地取决于项目:复杂性、用例、团队知识、项目何时开始生产等。
  • Saga可以帮助解决复杂的用例
  • SuspenseHooks值得考虑(或至少学习),特别是新项目 

就是这样——享受和快乐的编码!

 

原文地址:https://www.codeproject.com/Articles/1275809/Loading-data-in-React-redux-thunk-redux-saga-suspe

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值