使用 React/Redux 从 API 获取数据 从简单开始 thunk 是什么?Redux 怎么样?你刚才说了 thunk!那我们的应用呢?

2025-05-25

使用 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
    }
}
Enter fullscreen mode Exit fullscreen mode

因此,some_function被调用时,它会执行某些操作,然后返回一个带有命令和可能的数据的新函数以供稍后执行。

那么 Redux 怎么样?

我不想深入探讨 Redux 的深层含义(反正我估计也做不到),所以简单解释一下:它是 JavaScript 应用程序的状态容器。它将应用程序所需的所有数据都集中保存在同一个地方。应用程序中的每个组件都在状态容器中拥有自己的空间来查找数据。当数据发生变化时,组件也会随之变化。

行动

这个想法是,你将动作分派到 redux 上,并根据这些动作修改状态。

有趣的是:动作什么也不做。听起来好像有什么事情发生,但实际上什么也没有。动作只是一个带有type键的普通对象。就像这样:

// this is an action
{
    type: "SOME_ACTION",
    payload: {}
}
Enter fullscreen mode Exit fullscreen mode

大多数时候你不想一遍又一遍地写同一个对象,所以有一个叫做 Action Creators 的概念。

动作创造者

Action Creators 的作用正如其名,它们为您创建动作对象。

const SOME_ACTION = "SOME_ACTION";

function create_action(data) {
    return {
        type: SOME_ACTION,
        payload: data
    }
}
Enter fullscreen mode Exit fullscreen mode

因此,有了这些 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

更复杂的应用程序很可能有多个 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));

Enter fullscreen mode Exit fullscreen mode

让我们买一些产品

我们希望通过 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
    }
}

Enter fullscreen mode Exit fullscreen mode

现在我们有了动作创建者,让我们为整个事情设置减速器。


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

Enter fullscreen mode Exit fullscreen mode

好的,现在我们已经完成了大部分工作。

上面代码中需要注意的是 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;

Enter fullscreen mode Exit fullscreen mode

上面的操作非常简单。首先,我们调度待处理的操作。然后,我们从 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 );

Enter fullscreen mode Exit fullscreen mode

这里有很多事情要做。我们编写一个标准的 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
PREV
使用 JavaScript 的媒体查询 window.matchMedia API 检查媒体查询是否匹配 监听更新 为什么要使用 JavaScript 的媒体查询?
NEXT
为什么你应该从你的网站中删除 Google Analytics(分析)