使用 axios 调用 API 时如何防止 UI 冻结

Posted

技术标签:

【中文标题】使用 axios 调用 API 时如何防止 UI 冻结【英文标题】:How to prevent UI freeze when calling API with axios 【发布时间】:2019-08-21 23:04:35 【问题描述】:

当我的组件使用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 );
                );
            );
    


Action.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);
        );
);

reducer.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;
    
;

【问题讨论】:

回调、承诺或异步/等待或多或少是一回事...处理异步请求...这将需要一些时间...您需要做的是保持跟踪是“加载状态”,而加载是真实的显示/渲染一个微调器,让用户知道发生了什么,而 UI 只是冻结... 也许这就是我当时编写组件的方式。我的整个十二个输入形式被锁定似乎很奇怪,因为一个选择器需要拨打电话。我希望他们至少可以在等待的时候在其他输入中输入信息 看看这篇文章...我想它应该会有所帮助...***.com/questions/55204422/… @P-RickStephens 你的问题解决了吗? 【参考方案1】:

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

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

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

所以不要这样:

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

试试这个:

componentDidMount() 
  this.props.fetchTasks();

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

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

所以通常情况下,组件通常负责通过调用动作创建者来获取数据,但它是发出 API 请求的动作创建者,所以你在哪里进行异步 javascript 操作,你要去哪里实现 ES7 async/await 语法。

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

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

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 函数,将在您调度的每个操作中调用。中间件可以阻止 action 进入你的 reducer,修改 action 等等。

【讨论】:

这个答案需要更多的爱,精彩的总结,谢谢!【参考方案2】:

您设置了dispatch(setIsFetchingTasks(true)),但是当 axios 返回时,您从未将其设置为 false。您是否错过在return response.data; 之前添加dispatch(setIsFetchingTasks(false))? 这可能是您的 UI 等待 fetchingTasks 完成的原因

【讨论】:

添加所有数据时在reducer中设置。如果数据在那里,则必须完成提取 这就是你的 UI 冻结的原因。如果 UI 依赖于 setIsFetchingTasks,它将停止,直到您将其设置为 false(即 axios Promise 被履行或拒绝时)。基本上,您正在让您请求某种“同步”。你同意吗?您可以做的是在后台执行请求时显示加载屏幕 你的话对我来说没有意义。我将减速器添加到原始评论中,以便您查看。我不明白如何在减速器中将其设置为 false 而不是操作导致挂起。 问题不是在reducer或者action中设置为false。问题是使您的 UI 依赖于该布尔值。如果我理解正确,您是在告诉您的 UI 等到 isFetching 设置为 false ,因此只要异步请求完成,它就会冻结【参考方案3】:

我花了几个小时将我的代码从正常的 async/wait 重构为使用 redux thunk 来意识到问题实际上发生在 Deserializer

如果你有类似这样的代码:

import  Deserializer  from 'jsonapi-serializer'

new Deserializer( keyForAttribute: 'underscore_case' )
    .deserialize(response.data, deserialized =>  
      return deserialized 
)

反序列化器始终处于同步模式,因此 UI 会一直等到同步模式 完成

我不确定如何使其异步。 如果有人能告诉我,那就太好了。谢谢

【讨论】:

以上是关于使用 axios 调用 API 时如何防止 UI 冻结的主要内容,如果未能解决你的问题,请参考以下文章

Axios - 防止在外部 api 调用上发送 JWT 令牌

防止 nuxt auth 将授权标头发送到外部 url

从Vue js通过axios调用谷歌地图海拔api时如何修复跨域读取阻塞?

在 Vue.js 3 中通过 Axios 调用 API 登录时如何隐藏凭据?

vue中element-ui 使用axios上传文件 组件

vue中element-ui 使用axios上传文件 组件