如何确保如果已经有 API 调用正在进行中,我不会再次调用我的 API(通过单击按钮)?



我做了这段代码,我有一个问题。

在阅读问题之前,请参阅下面的片段。

它通过调用 API 来模仿博客文章的喜欢/不喜欢操作,我认为它按预期工作,但我不知道确切的原因。我认为这很有趣。

这是我的思路:

  • 所有渲染完成后,我有一个"单击"侦听器附加到按钮,它将由我的函数处理toggleLike
  • 在我第一次点击按钮之前,我的toggleLike函数,此时,它是在上次渲染期间创建的,当时props.likeInProgress的值false。因此,如果我在这一点上执行这个函数 10 次,它会认为props.likeInProgressfalse
  • 我知道一旦单击按钮toggleLike要做的第一件事就是通过调用setLikeInProgress(true);likeInProgress设置为true,这将阻止我进行两个 API 调用一个在另一个之上。
  • 但我也知道setState可能不会立即触发重新渲染,那么是什么阻止我疯狂地快速点击并最终同时调用 API 两到三次呢?

问题

事实是,似乎不可能足够快地点击并同时触发多个 API 调用(同时运行(。这就是我想要实现的目标。

但我想知道的是:这是速度问题吗?React 更新状态的速度是否如此之快,以至于在重新创建toggleFunction之前无法再次单击该按钮(现在props.likeInProgress为 true(?或者代码的执行顺序阻止了这种情况完全发生,它与速度无关?

更多解释:

当您非常快速地单击 2 次时,您看到There is a call in progress日志的事实意味着 React 已经重新渲染了所有内容,现在toggleLike函数将props.likeInProgress视为true。如果这是一个速度问题,并且您点击得非常快,您将能够从第一次点击开始执行完全相同的toggleLike,这将props.likeInProgress视为false,这将导致您第二次调用 API。

function App() {

//console.log('Rendering App...');
// STATE TO MONITOR THE LIKE API CALL IN PROGRESS
const [likeInProgress,setLikeInProgress] = React.useState(false);

// STATE TO KNOW WHETHER A POST HAS BEEN LIKED OR NOT
const [hasLiked,setHasLiked] = React.useState(false);

// REF TO KEEP TRACK OF THE LIKE COUNT
const likeCount_ref = React.useRef(15);

// THIS IS TO MOCK A LIKE API CALL (1000 ms DELAY ASYNC)
function mockLikeAPI(action) {
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('Done');
},1000);
});
}
// THIS IS THE CALL TO THE LIKE API
function callMockLikeAPI(action) {
setLikeInProgress(true);                              // SET STATE likeInProgress TO 'TRUE'
mockLikeAPI(action).then(()=> {                       // CALL THE API
likeCount_ref.current = action === 'LIKE' ?         // UPDATE COUNTER ACCORDINGLY
likeCount_ref.current + 1 
: likeCount_ref.current - 1;
action === 'LIKE' ? setHasLiked(true) : setHasLiked(false);   // UPDATE hasLiked STATE
setLikeInProgress(false);
});
}
return(
<BlogPost
likeCount={likeCount_ref.current}
likeInProgress={likeInProgress}
hasLiked={hasLiked}
callMockLikeAPI={callMockLikeAPI}
/>
);
}
function BlogPost(props) {
//console.log('Rendering BlogPost...');

// FUNCTION TO TOGGLE THE LIKE OF THE POST
function toggleLike() {
if (props.likeInProgress) {                       // IF THERE'S A LIKE IN PROGRESS
console.log('There is a call in progress...');  // LOG
return;                                         // DO NOTHING. RETURN;
}
else {                                        // ELSE
console.log('I will call the API now...');  // LOG
props.hasLiked === false ?                  // CALL THE API WITH THE PROPER ACTION
props.callMockLikeAPI('LIKE') 
: props.callMockLikeAPI('DISLIKE');
}
}

return(
<React.Fragment>
<div>I am a Blog Post</div>
<div>Like Count: {props.likeCount}</div>
<button onClick={toggleLike}>{props.hasLiked === false ? 'Like' : 'Dislike'}</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

用户单击按钮后将其隐藏,将其替换为 gif 加载器,然后在调用完成后再次显示它。您需要在网络调用上实现回调。这将保证用户无法call the api twice,因为带有 onclick 事件的按钮现在处于隐藏状态。

btnWithNetCall(parameter1){
// hide your button via attribute or css
attemptAPIcall(param1, function(response){
//show your button again after checking what happened with response
})
} 

attemptAPIcall(param1, callback){
// fire callback with pertinent data after doing networky thing
callback(someSortOfReponseData)
}

您不必担心渲染之间的点击。

您可以在单击后禁用该按钮并添加某种进度条/加载器。

function App() {
const [state, setState] = React.useState({
likeInProgress: false,
hasLiked: false,
likes: 15
});
function mockLikeAPI(action) {
return new Promise((resolve, reject) => setTimeout(() => resolve("Done"), 1000));
}
async function callMockLikeAPI(action) {
setState(cs => ({ ...cs, likeInProgress: true }));
await mockLikeAPI(action);
let actionIsLike = action === "LIKE";
setState({
likeInProgress: false,
hasLiked: actionIsLike,
likes: actionIsLike ? state.likes + 1 : state.likes - 1,
});
}
return (
<BlogPost
likeCount={state.likes}
likeInProgress={state.likeInProgress}
hasLiked={state.hasLiked}
callMockLikeAPI={callMockLikeAPI}
/>
);
}
function BlogPost(props) {
function toggleLike() {
if (props.likeInProgress) {
return;
} else {
props.hasLiked === false
? props.callMockLikeAPI("LIKE")
: props.callMockLikeAPI("DISLIKE");
}
}
return (
<React.Fragment>
<div>I am a Blog Post</div>
<div>Like Count: {props.likeCount}</div>
<button disabled={props.likeInProgress} onClick={toggleLike}>
{props.hasLiked === false ? "Like" : "Dislike"}
{props.likeInProgress ? <div className='loader' /> : ''}
</button>
</React.Fragment>
);
}
ReactDOM.render(<App />, document.body);
.loader {
border: 1px solid #f3f3f3; 
border-top: 1px solid #3498db;
border-radius: 50%;
width: 10px;
height: 10px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>

https://github.com/facebook/react/issues/10231#issuecomment-316644950

在当前版本中,如果您位于 反应事件处理程序。React 批处理在 React 期间完成的所有设置状态 事件处理程序,并在退出自己的浏览器之前应用它们 事件处理程序。

对于某些事件,包括点击,react 确保在退出事件处理程序之前呈现。因此,在您第一次点击后,渲染将在下一次 onClick 触发之前与likeInProgress = true一起发生。

最新更新