首页 > 解决方案 > 使用 axios 调用 API 时如何防止 UI 冻结

问题描述

当我的组件使用加载时,我正在尝试加载数据componentDidMount。但是调用 Redux 操作,使用 axios 进行调用似乎会冻结 UI。当我有一个包含 12 个输入的表单并且一个进行 API 调用时,我会假设我可以输入其他输入并且不会让它们冻结在我身上。

我已经尝试阅读有关该主题的其他一些帖子,但它们都有点不同,我尝试过的一切似乎都无法解决问题。

我正在使用 React 16.8 在 linux 上工作(使用 RN 时我使用 55.4)

我尝试过使我的componentDidMount异步以及 redux-thunk 动作。它似乎没有任何帮助,所以我一定做错了什么。

我尝试执行以下操作但没有成功。只是对我尝试的内容使用简短的形式。下面列出的实际代码。

async componentDidMount() {
    await getTasks().then();
}

我试过这个

export const getTasks = () => (async (dispatch, getState) => {
    return await axios.get(`${URL}`, AJAX_CONFIG).then();
}

当前代码:

组件.js

componentDidMount() {
    const { userIntegrationSettings, getTasks } = this.props;
    // Sync our list of external API tasks
    if (!isEmpty(userIntegrationSettings)) {
        getTasks(userIntegrationSettings.token)
            // After we fetch our data from the API create a mapping we can use
            .then((tasks) => {
                Object.entries(tasks).forEach(([key, value]) => {
                    Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
                });
            });
    }
}

动作.js

export const getTasks = () => ((dispatch, getState) => {
    const state = getState();
    const { token } = state.integrations;

    const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
    const AJAX_CONFIG = getAjaxHeaders(token);

    dispatch(setIsFetchingTasks(true));

    return axios.get(`${URL}`, AJAX_CONFIG)
        .then((response) => {
            if (!isEmpty(response.data)) {
                response.data.forEach((task) => {
                    dispatch(addTask(task));
                });

                return response.data;
            } else {
                dispatch(setIsFetchingTasks(false));
            }
        })
        .catch((error) => {
            dispatch(setIsFetchingTasks(false));
            errorConsoleDump(error);
            errorHandler(error);
        });
});

减速器.js

export default (state = defaultState, action) => {
    switch (action.type) {
        case ADD_TASK:
        case UPDATE_TASK:
            return update(state, {
                byTaskId: { $merge: action.task },
                isFetching: { $set: false }
            });
        default:
            return state;
    }
};

标签: reactjsreact-nativereduxredux-thunk

解决方案


所以在我的回答中,你要学什么?

  • 使用 Redux 加载常规数据
  • 设置组件生命周期方法,例如componentDidMount()
  • componentDidMount()
  • 动作创建者运行代码来发出 API 请求
  • API 响应数据
  • 动作创建者返回一个action带有获取的payload属性数据

好的,所以我们知道有两种方法可以在 Reactjs 应用程序中初始化状态,我们可以调用constructor(props)函数或调用组件生命周期方法。在这种情况下,我们可以假设是基于类的函数来执行组件生命周期方法。

所以代替这个:

async componentDidMount() {
  await getTasks().then();
}

尝试这个:

componentDidMount() {
  this.props.fetchTasks();
}

所以动作创建者 (fetchTasks()) 状态值变成了组件this.props.fetchTasks();所以我们确实从 调用动作创建者componentDidMount(),但通常不是你这样做的方式。

异步操作发生在您的动作创建者内部,而不是您的componentDidMount()生命周期方法中。您的componentDidMount()生命周期方法的目的是在启动应用程序时启动该操作创建者执行操作。

所以通常情况下,组件通常负责通过调用动作创建者来获取数据,但它是发出 API 请求的动作创建者,因此您将在其中进行异步 JavaScript 操作,并且您将在那里实现ES7 异步/等待语法。

所以换句话说,它不是启动数据获取过程的组件生命周期方法,这取决于动作创建者。组件生命周期方法只是调用启动数据获取过程的操作创建者,也就是异步请求。

需要明确的是,在将动作创建者导入组件并导入连接函数后,您可以this.props.fetchTasks();从生命周期方法中调用,如下所示:componentDidMount()

import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";

你从来没有提供你正在做这一切的组件的名称,但是在那个文件的底部你需要做export default connect(null, { fetchTasks })(ComponentName);

我留下第一个论点是null因为你必须通过mapStateToProps,但由于我不知道你有没有,所以你null现在可以通过。

而不是这个:

export const getTasks = () => (async (dispatch, getState) => {
    return await axios.get(`${URL}`, AJAX_CONFIG).then();
}

尝试这个:

export const fetchTasks = () => async dispatch => {
  const response = await axios.get(`${URL}`, AJAX_CONFIG);
  dispatch({ type: "FETCH_TASKS", payload: response.data });
};

getState如果您不打算使用它,则无需在您的动作创建器中进行定义。在开发异步动作创建器时,您还缺少所需的dispatch()方法。该dispatch()方法将调度该操作并将其发送到应用程序内的所有不同减速器。

这也是 Redux-Thunk 等中间件发挥作用的地方,因为动作创建者无法处理开箱即用的异步请求。

你没有展示你是如何连接你的 redux-thunk 的,但它通常放在你的根index.js文件中,它看起来像这样:

import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

import App from "./components/App";
import reducers from "./reducers";

const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")

还记得我说你需要实现的连接函数吗?这是实施的结果,或者您应该实施Provider标签。使用Provider标签,您的组件都可以访问 Redux 存储,但是为了将数据连接到您的组件,您需要导入连接函数。

connect 函数返回到Provider并告诉它它想要访问您拥有该生命周期方法的任何组件内的该数据。

如果您按照我上面的建议纠正了所有内容,那么 Redux-Thunk 绝对是您需要实现的。

为什么需要 Redux-Thunk?

它没有任何内在内置的东西,它只是一个通用的中间件。它所做的一件事是允许我们处理动作创建者,这是您需要它为您做的事情。

通常,动作创建者返回一个动作对象,但使用 redux-thunk,动作创建者可以返回一个动作对象或函数。

如果您返回一个动作对象,它必须仍然具有type您在上面的代码示例中看到的属性,并且它也可以选择具有一个payload属性。

Redux-Thunk 允许您在动作创建器中返回动作或函数。

但为什么这很重要?谁在乎它是返回一个动作对象还是一个函数?有什么关系?

这又回到了异步 JavaScript 的话题,以及 Redux 中的中间件如何解决 Redux 无法处理开箱即用的异步 JavaScript 的问题。

因此,同步动作创建者会立即返回一个动作,其中包含准备好的数据。但是,当我们与这种情况下的异步操作创建者一起工作时,它需要一些时间才能准备好其数据。

因此,任何发出网络请求的动作创建者都有资格作为异步动作创建者。

使用 JavaScript 的网络请求本质上是异步的。

所以 Redux-Thunk 是一个中间件,它是一个 JavaScript 函数,你调度的每一个动作都会被调用。中间件可以阻止动作继续到你的减速器,修改动作等等。


推荐阅读