使用 React/Redux 从 API 获取数据
从简单开始
这是什么东西?
那么 Redux 怎么样?
您谈到了 thunks!?
我们的应用程序怎么样?
从简单开始
这是我在这里的第一篇文章。我决定分享一些我通过犯下所有你可能犯的错误而获得的知识——我在这里写的所有东西都是通过阅读博客文章、尝试理解所做的事情以及反复尝试而学到的。如果有错误,或者你想到了更好的方法,请在评论区告诉我。我总是很感激有用的建议!
现在,首先要做的事情是:你需要安装 React 和 Redux。我假设你已经知道如何操作了。设置好 React 应用后,你需要使用以下命令安装一个名为 redux-thunk 的工具:npm install redux-thunk
安装完所有这些后,我们现在可以看看实现奇迹所需的组件!
这是什么东西?
基本上,thunk 就是一个函数被另一个函数调用。等等……什么?是的,我第一次听到这个说法时就是这么想的。我给你举个例子:
function some_function() {
// do something
return function thunk() {
// do something thunky later
}
}
因此,some_function
被调用时,它会执行某些操作,然后返回一个带有命令和可能的数据的新函数以供稍后执行。
那么 Redux 怎么样?
我不想深入探讨 Redux 的深层含义(反正我估计也做不到),所以简单解释一下:它是 JavaScript 应用程序的状态容器。它将应用程序所需的所有数据都集中保存在同一个地方。应用程序中的每个组件都在状态容器中拥有自己的空间来查找数据。当数据发生变化时,组件也会随之变化。
行动
这个想法是,你将动作分派到 redux 上,并根据这些动作修改状态。
有趣的是:动作什么也不做。听起来好像有什么事情发生,但实际上什么也没有。动作只是一个带有type
键的普通对象。就像这样:
// this is an action
{
type: "SOME_ACTION",
payload: {}
}
大多数时候你不想一遍又一遍地写同一个对象,所以有一个叫做 Action Creators 的概念。
动作创造者
Action Creators 的作用正如其名,它们为您创建动作对象。
const SOME_ACTION = "SOME_ACTION";
function create_action(data) {
return {
type: SOME_ACTION,
payload: data
}
}
因此,有了这些 action creators,您现在可以SOME_ACTION
通过调用轻松使用create_action(data)
。可以使用 将这些 action creators 调度到 redux dispatch(create_action(data))
。
Reducers
一个 action 被分发后,会被传递给一个叫做 Reducer 的函数。Reducer 是一个函数,它被赋予一个状态和一个 action。它会根据 action 转换状态,然后返回新的状态。
function someReducer(state, action) {
switch(action.type) {
case SOME_ACTION:
return {
...state,
data: action.payload
}
break;
default:
// the dispatched action is not in this reducer, return the state unchanged
return state;
}
}
更复杂的应用程序很可能有多个 Reducer,每个 Reducer 负责状态的某个部分。因此,务必记住 Reducer 默认返回状态不变的情况。
需要注意的是,Reducer 是纯函数。它们不会调用 API 之类的东西,也不会将其他 action 分发给 Redux。
您谈到了 thunks!?
你记住了。好的,又是 Thunk。我刚才提到 Reducer 是纯函数。但我们经常需要根据数据或其他因素进行某种 API 调用或调度……但这做不到……Reducer 是纯函数……Redux-Thunk 来帮你!
Redux-Thunk 很容易理解。它就是 Redux Store 的中间件。它会检查每个被 dispatch 的 action,如果是函数,就调用该函数。除此之外,没有其他功能了。但这却开启了一个全新的世界,让各种奇特的 action 都能 dispatch 到 Redux。
您可能会问,我怎样才能将这个小奇迹带入我的商店?
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './rootReducer';
import initialState from './initialState';
const middlewares = [thunk];
createStore(rootReducer, initialState, applyMiddleware(...middlewares));
让我们买一些产品
我们希望通过 API 加载一些产品。为此,我们首先将组件设置为某种待处理状态,并显示一个加载旋转图标或类似的效果。然后加载数据,并决定是否只显示产品列表或显示某种错误消息。
我们首先设置我们的动作创建者。
// action.js
export const FETCH_PRODUCTS_PENDING = 'FETCH_PRODUCTS_PENDING';
export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS';
export const FETCH_PRODUCTS_ERROR = 'FETCH_PRODUCTS_ERROR';
function fetchProductsPending() {
return {
type: FETCH_PRODUCTS_PENDING
}
}
function fetchProductsSuccess(products) {
return {
type: FETCH_PRODUCTS_SUCCESS
products: products
}
}
function fetchProductsError(error) {
return {
type: FETCH_PRODUCTS_ERROR
error: error
}
}
现在我们有了动作创建者,让我们为整个事情设置减速器。
// reducer.js
import {FETCH_PRODUCTS_PENDING, FETCH_PRODUCTS_SUCCESS, FETCH_PRODUCTS_ERROR} from './actions';
const initialState = {
pending: false,
products: [],
error: null
}
export function productsReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_PENDING:
return {
...state,
pending: true
}
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
pending: false,
products: action.payload
}
case FETCH_PRODUCTS_ERROR:
return {
...state,
pending: false,
error: action.error
}
default:
return state;
}
}
export const getProducts = state => state.products;
export const getProductsPending = state => state.pending;
export const getProductsError = state => state.error;
好的,现在我们已经完成了大部分工作。
上面代码中需要注意的是 Reducer 末尾的三个函数。它们被称为选择器。选择器用于获取状态的已定义部分。在小型应用中,它们有些过度。但是,如果你的应用规模不断扩大,变得越来越复杂,那么在状态中更改某些内容就会变得非常混乱。使用选择器时,你只需更改选择器即可,一切正常。
我可能会写一篇关于选择器的博客文章,因为我认为它们对于建立可扩展的 react/redux 应用程序非常重要。
现在我们说到哪儿了……啊,是的,大部分工作已经完成了。在 redux 方面,我们唯一要做的就是写一个新奇的 action。
// fetchProducts.js
import {fetchProductsPending, fetchProductsSuccess, fetchProductsError} from 'actions';
function fetchProducts() {
return dispatch => {
dispatch(fetchProductsPending());
fetch('https://exampleapi.com/products')
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(fetchProductsSuccess(res.products);
return res.products;
})
.catch(error => {
dispatch(fetchProductsError(error));
})
}
}
export default fetchProducts;
上面的操作非常简单。首先,我们调度待处理的操作。然后,我们从 API 中获取数据。我们将传入的 JSON 解码为一个对象。然后,我们检查是否存在错误。如果发生错误,我们会抛出错误并调用错误函数。如果一切顺利,我们会调用成功操作。剩下的交给 Reducer 处理。
这都是关于从服务器获取数据的……不,开玩笑的,不是这样的。不过大多数关于从 API 端获取数据的帖子都是这样写的,对吧?但是……
我们的应用程序怎么样?
哦,你想让你商店里的商品真正显示在 React 应用中吗?好的,我们开始吧。
我假设你已经知道如何使用提供程序将你的 React 应用连接到 Redux Store。网上有很多关于这个主题的文章。完成之后,你需要一些组件。
对我来说,一切都始于视图。对我来说,视图是一个组件,它将用户获取的所有内容封装到一个父组件中。这个父组件与 Redux Store 的大部分连接都与它封装的组件共享数据。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import fetchProductsAction from 'fetchProducts';
import {getProductsError, getProducts, getProductsPending} from 'reducer';
import LoadingSpinner from './SomeLoadingSpinner';
import ProductList from './ProductList';
class ProductView extends Component {
constructor(props) {
super(props);
this.shouldComponentRender = this.shouldComponentRender.bind(this);
}
componentWillMount() {
const {fetchProducts} = this.props;
fetchProducts();
}
shouldComponentRender() {
const {pending} = this.props;
if(this.pending === false) return false;
// more tests
return true;
}
render() {
const {products, error, pending} = this.props;
if(!this.shouldComponentRender()) return <LoadingSpinner />
return (
<div className='product-list-wrapper'>
{error && <span className='product-list-error'>{error}</span>}
<ProductList products={products} />
</div>
)
}
}
const mapStateToProps = state => ({
error: getProductsError(state),
products: getProducts(state),
pending: getProductsPending(state)
})
const mapDispatchToProps = dispatch => bindActionCreators({
fetchProducts: fetchProductsAction
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductView );
这里有很多事情要做。我们编写一个标准的 React 组件。然后使用connect
它将其连接到我们的 Redux Store。Connect
它接受两个参数:一个函数mapStateToProps
将部分状态映射到组件的 props 中,另一个函数mapDispatchToProps
将函数映射到 props 中,当调用时,这些 props 会被分发到 Redux。
最后,我们将所有这些东西放在一起,瞧,我们就与我们的商店建立了联系。
在 mapStateToProps 函数中,我们使用了之前编写的选择器。
shouldComponentRender
我喜欢在我的视图组件以及大多数组件中添加一个名为的函数。我这样命名它,是因为它与 React 的shouldComponentUpdate
生命周期方法类似。它会检查组件是否应该渲染。如果不需要,就渲染一个 LoadingSpinner 组件。
我发现这种工作方式非常有益,因为组件总是会重新初始化,并且所有子组件都会在 pending 标志(在本例中控制渲染)切换后重新挂载。因此,你可以在构造函数中将 Redux 状态添加到组件的状态中。(我不想讨论 Redux 和组件状态中的内容,这是另一篇文章的主题)。
在我的大多数项目中,我发现这是最烦人的问题之一。想象一下一个渲染产品的组件。它由数据初始化,然后一些子组件(例如具有组件状态的价格计算器)在其构造函数中初始化。当新数据进入时,您需要检查计算器是否需要重新初始化。使用这个shouldComponentRender
函数,做到这一点非常容易。每次待处理标志切换时(可能是因为选择了新产品),所有内容都会重新初始化,一切就绪。
当然,由于某些原因,您的视图中的组件可能无法重新渲染。如果是这种情况,只需shouldComponentRender
从视图中移除该函数,并在子组件中使用它即可。
您可以使用某种淡出/淡入效果来改善用户体验。
好了,就这样了。这次是真的了。
感谢您阅读我的第一篇博文。希望您喜欢它,也希望您有所收获。如果您有任何关于如何提升 React/Redux 技能的建议或技巧,或者只是想打个招呼,欢迎留言,我非常乐意。
文章来源:https://dev.to/markusclaus/fetching-data-from-an-api-using-reactredux-55ao