在React中加载数据:redux-thunk,redux-saga,suspense,hooks
目录
介绍
初始设置
模拟服务器
项目和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(减速器)+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 <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