即将到来的并发模式是否打破了旧的保证,即单击处理程序中的setState
更新在事件边界同步刷新?
例如,如果我有一个按钮,应该只按一次,一个假定的工作模式是“在点击处理程序中设置状态为禁用”:
let counter = 0;
const C = () => {
const [disabled, setDisabled] = React.useState(false);
const handler = React.useCallback(
() => { setDisabled(true); counter++; },
[], // setDisabled is guaranteed to never change
);
return (<button onClick={handler} disabled={disabled}>click me</button>);
};
// Assert: `counter` can never be made >1 by clicking the button with one C
这种模式过去是可以保证工作的(至少考虑到设置disabled属性可以防止任何进一步的点击事件,情况似乎就是这样)。我能找到的最大的相关问题讨论了这一点,也展示了一个或多或少明显的替代方法(并且更容易证明它是有效的),即使用ref(与链接问题中的答案不同,可能更像是布尔ref,但同样的想法,它总是同步的)。
旁白:这些信息是最新的,还是有什么变化?毕竟已经三年多了。它提到“互动事件(如点击)”,其他的是什么?
然而,在并发模式下,渲染可以暂停,我解释为“js线程将被释放”,以允许潜在的按键或任何事件渗入,在这个阶段,在下一个渲染禁用按钮之前,还可能发生额外的点击事件...因此,是使用某种ref的方法,还是显式添加ReactDOM. flushSync?
我目前对并发模式工作原理的理解是:
1-重新渲染开始
2个钩子会改变内部状态
3a-重新渲染暂停
4a-内部状态更改被回滚
或
3b-重新渲染未暂停
4b-提交内部状态更改
useCallback
是useMoom
上的一个薄包装器,使用“内部状态”保存缓存的值。(4a)是这里的关键,据我所知,您的解决方案不再保证有效。
useRef
(带有布尔标志值)解决方案也存在同样的问题,因为您不能保证在暂停重新渲染时ref的新值实际上会“提交”。
在useRef
解决方案中,您保留DOM按钮元素的ref并直接操作禁用的
属性,即使在并发模式下也仍然有效。React无法阻止您直接操作DOM。
“挂起”意味着恢复“内部状态”,而不应用生成的DOM操作,并不意味着任何副作用(如直接操作DOM)都会受到影响。
flushSync
也不会有帮助,它只是强制重新渲染,并不保证当前渲染不会挂起。
据我所知,setState
调用始终是异步的,并且您从来没有办法保证按钮在单击后立即被禁用。JS中也没有并发,它只有一个线程,问题是渲染可能比你预期的要晚,所以你可以收到另一个点击,直到React为你重新渲染。
如果你只需要启动逻辑一次,我会建议使用useRef
钩子,当你需要确保我们没有点击按钮时,只需检查值。
const isDisabled = useRef(false);
const onClick = () => {
if (!isDisabled.current) {
isDisabled.current = true;
}
}