使用 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 令牌
从Vue js通过axios调用谷歌地图海拔api时如何修复跨域读取阻塞?