我做了这段代码,我有一个问题。
在阅读问题之前,请参阅下面的片段。
它通过调用 API 来模仿博客文章的喜欢/不喜欢操作,我认为它按预期工作,但我不知道确切的原因。我认为这很有趣。
这是我的思路:
- 所有渲染完成后,我有一个"单击"侦听器附加到按钮,它将由我的函数处理
toggleLike
- 在我第一次点击按钮之前,我的
toggleLike
函数,此时,它是在上次渲染期间创建的,当时props.likeInProgress
的值false
。因此,如果我在这一点上执行这个函数 10 次,它会认为props.likeInProgress
false
。 - 我知道一旦单击按钮,
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
一起发生。