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

react

目录

介绍

初始设置

模拟服务器

项目和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!的代码。它只是添加了一些“语法糖”,仅此而已。

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

每次执行动作时,减速器都会相应地改变状态。组件将状态映射到属性,并在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 <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);

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

加载指标

数据

错误

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

优点:

  • react/redux 应用程序的“推荐”方法。

  • 没有其他依赖项。差不多,thunk很小:)

  • 无需学习新事物。

缺点:

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

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 <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

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 <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创建并使用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 <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可以帮助解决复杂的用例

  • Suspense和Hooks值得考虑(或至少学习),特别是新项目 

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

 

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

以上是 在React中加载数据:redux-thunk,redux-saga,suspense,hooks 的全部内容, 来源链接: utcz.com/z/381108.html

回到顶部