Javascript 单击处理程序中setState的并发模式和同步刷新

Javascript 单击处理程序中setState的并发模式和同步刷新,javascript,reactjs,Javascript,Reactjs,即将到来的并发模式是否打破了旧的保证,即单击处理程序中的setState更新在事件边界同步刷新 如果我有一个按钮,该按钮只应按下一次,那么一种假定的工作模式是“在单击处理程序中将状态设置为禁用”: let计数器=0; 常数C=()=>{ const[disabled,setDisabled]=React.useState(false); const handler=React.useCallback( ()=>{setDisabled(true);计数器++;}, [],//setDisabl

即将到来的并发模式是否打破了旧的保证,即单击处理程序中的
setState
更新在事件边界同步刷新


如果我有一个按钮,该按钮只应按下一次,那么一种假定的工作模式是“在单击处理程序中将状态设置为禁用”:

let计数器=0;
常数C=()=>{
const[disabled,setDisabled]=React.useState(false);
const handler=React.useCallback(
()=>{setDisabled(true);计数器++;},
[],//setDisabled保证永远不会更改
);
返回(点击我);
};
//断言:`counter`不能通过单击带有一个C的按钮使其大于1
这种模式(至少假设设置disabled属性可以防止任何进一步的单击事件,情况似乎就是这样)。本文讨论了这一点,并展示了一个或多或少明显的替代方法(并且更容易证明它是有效的),即使用ref(与链接问题中的答案不同,可能更像是布尔ref,但同样的想法是,它总是同步的)

旁白:这些信息是最新的,还是有什么变化?毕竟已经三年多了。它提到“互动事件(如点击)”,其他的是什么


但是,在并发模式下,渲染可以暂停,我将其解释为“js线程将被释放”,以允许潜在的按键或任何事件进入,在该阶段,在下一次渲染禁用按钮之前,还可能发生其他单击事件。因此,是使用某种ref的方法,或者可能显式添加
ReactDOM.flushSync

据我所知,
setState
调用始终是异步的,并且您从来没有办法保证按钮在单击后立即被禁用。在JS中也没有并发性,它只有一个线程,问题是渲染可能比您预期的晚,因此您可以收到另一个单击,直到React为您重新渲染

如果您只需要触发一次逻辑,我建议使用
useRef
hook,当您需要确保没有单击按钮时,只需检查值

const isDisabled = useRef(false);
const onClick = () => {
     if (!isDisabled.current) {
        isDisabled.current = true;
     }
}

我目前对并发模式工作原理的理解是:

1-重新渲染开始

2-调用挂钩,它们改变内部状态

3a-重新渲染已暂停

4a-回滚内部状态更改

3b-重新渲染未暂停

4b-提交内部状态更改

useCallback
useMoom
上的一个薄包装器,使用“内部状态”保存缓存的值。(4a)是这里的关键,据我所知,您的解决方案不再保证有效

useRef
(带有布尔标志值)解决方案也存在同样的问题,因为您不能保证在暂停重新渲染时ref的新值实际上会“提交”

useRef
解决方案中,您保留对DOM按钮元素的引用,并直接操作
disabled
属性,即使在并发模式下,该解决方案仍然有效。React无法阻止您直接操作DOM

“挂起”意味着恢复“内部状态”+不应用生成的DOM操作,并不意味着任何副作用(如直接操作DOM)都会受到影响


flushSync
也不会有帮助,它只是强制重新渲染,并不保证当前渲染不会挂起。

很有趣,但您能否详细说明一下,为什么您认为即使是ref也无法工作(假设为通用引用单元格)?如前所述,正如另一个答案所建议的,我可能会对一个通用的可变引用单元格(或者,在实例上使用类组件和某个成员)执行一个
useRef(false)
,我认为react对此没有发言权。哦,对不起,我没有正确阅读您链接到的答案。看起来ref指向的是实际的DOM按钮。这将100%仍然有效。React无法阻止直接DOM操作。我错误地假设ref解决方案使用bool ref值。该版本在并发模式下确实会被破坏,原因与
useMemo
one会被破坏的原因相同。编辑:更新我的答案以澄清这一点。再说一次,我的错,没有正确阅读该答案。要清楚,在另一个答案中提出的解决方案肯定不会在并发模式下工作,因为
isDisabled.current=true不保证提交。如果单击处理程序内部正在执行
isDisabled.current=true
,则应在任何
setState
之前执行,因此在任何渲染阶段开始之前执行,react是否可以撤消它?我有点惊讶,react甚至触及了由
useRef
获得的可变引用单元格,如果它们没有作为ref属性或类似属性传递。无论何时调用
setState
或何时更改ref的值,重要的是它的效果不会被提交(并且在渲染调用中可见)直到下一个渲染完成而不挂起。否则,您将以不一致的状态结束。考虑一下
useffect
:如果允许提交
setState
,但没有实际应用DOM操作,那么下次渲染后
useffect
将看到哪个版本的状态?更糟糕的是,您没有更新的DOM中可能的引用又如何呢?
const isDisabled = useRef(false);
const onClick = () => {
     if (!isDisabled.current) {
        isDisabled.current = true;
     }
}