您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 1浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

寒冰屋 发布时间:2019-02-01 21:16:29 ,浏览量:1

目录

介绍

初始设置

模拟服务器

项目和API调用

Redux-thunk

Redux-saga 

Suspense

Hooks

结论

介绍

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

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

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

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

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

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.创建reduser(redux-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 
Loading
} if (this.props.error) { return { color: 'red' }}ERROR: {this.props.error}
} return ( First Name Last Name Active? Posts Messages {this.props.data.map(u => {u.firstName} {u.lastName} {u.active ? 'Yes' : 'No'} {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);

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

加载指标

https://www.codeproject.com/KB/scripting/1275809/e85ac0e0-9231-4c21-aa10-c66708567aa6.Png

数据

https://www.codeproject.com/KB/scripting/1275809/1ad4767f-fec5-4df9-89cf-33930de4da58.Png

错误

https://www.codeproject.com/KB/scripting/1275809/0e407ae8-8fd1-4af3-b1a1-fa39119a7311.Png

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

优点:

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

缺点:

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

https://www.codeproject.com/KB/scripting/1275809/0e4ed027-e72a-4a90-88c9-954cc2b50f3c.Png

Redux-saga 

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

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

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

1.安装saga

npm install redux-thunk

2.添加saga中间件并添加所有sagas(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行的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.创造sagas(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.创建reducer(redux-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;
       }
   }
}

Reducer与thunk示例完全相同。

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 
Loading
} if (this.props.error) { return {color: 'red'}}ERROR: {this.props.error}
} return ( First Name Last Name Active? Posts Messages {this.props.data.map(u => {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(减速器)+sagas(33)+ 60(组件))。

优点:

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

缺点:

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

Suspense是React 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 { color: 'red' }}ERROR: {this.state.error || 'Unexpected Error'}
; } 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 (
           
               
               
                   First Name
                   Last Name
                   Active?
                   Posts
                   Messages
               
               
               
               {users.map(u =>
                   
                       {u.firstName}
                       {u.lastName}
                       {u.active ? 'Yes' : 'No'}
                       {u.posts}
                       {u.messages}
                   
               )}
               
           
       );
   }
}

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 (
           
               
                   
               
           
       );
   }
}

export default UsersWithSuspense;

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

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

优点:

缺点:

Hooks

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

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

1创建并使用hooks(hooks / UsersWithHooks.js)

这里我们创建了3个hooks(函数)来“挂钩”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 
Loading
} if (error) { return {color: 'red'}}ERROR: {error}
} return ( First Name Last Name Active? Posts Messages {data.map(u => {u.firstName} {u.lastName} {u.active ? 'Yes' : 'No'} {u.posts} {u.messages} )} ); } export default UsersWithHooks;

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

优点:

缺点:

结论

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

 

文件

代码行

依赖

Redux需要吗?

Thunk

3

109

0.001

yes

Saga

4

136

1

yes

Suspense

4/3

106/87

0

no

Hooks

1

56

0

no

 

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

 

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

关注
打赏
1665926880
查看更多评论

最近更新

热门博客

立即登录/注册

微信扫码登录

0.0466s