【问题标题】:Concurrent-mode and sync flushing of setState in click handlers单击处理程序中 setState 的并发模式和同步刷新
【发布时间】:2021-07-28 02:28:12
【问题描述】:

即将到来的并发模式是否打破了旧的保证,即点击处理程序中的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

这种模式used to be guaranteed to work(至少考虑到设置 disabled-attribute 可以防止任何进一步的点击事件,这似乎是这种情况)。 biggest related question i could find 讨论了这一点,并且还展示了一个或多或少明显的替代方案(并且更容易证明它有效),即使用参考(与链接问题中的答案不同,可能是布尔参考,但同样的想法,它总是同步)。

附带问题:这些信息是最新的,还是发生了变化?毕竟已经三岁多了。它提到了“交互事件(如点击)”,其他是什么?

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

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    我目前对并发模式如何工作的理解是这样的:

    1 - 重新渲染开始

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

    3a - 暂停重新渲染

    4a - 内部状态更改回滚

    3b - 重新渲染未暂停

    4b - 内部状态更改已提交

    useCallbackuseMemo 的精简包装器,并使用“内部状态”来保存缓存值。 (4a) 是这里的关键,据我了解,您的解决方案不能再保证有效。

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

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

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

    flushSync 也无济于事,它只是强制重新渲染,不保证当前渲染不会暂停。

    【讨论】:

    • 有趣,但你能详细说明一下,为什么你认为即使是 ref 也不起作用(假设通用参考单元格)?如前所述,就像另一个答案所暗示的那样,我可能会为一个通用的可变参考单元格做一个useRef(false)(或者,在实例上使用一个类组件和一些成员),我不认为反应有有什么说的。
    • 哦,对不起,我没有正确阅读您链接到的答案。看起来 ref 指向实际的 DOM 按钮。这将 100% 仍然有效。 React 无法阻止直接的 DOM 操作。我错误地假设 ref 解决方案使用 bool ref 值。出于与useMemo 相同的原因,该版本确实会在并发模式下被破坏。编辑:更新了我的答案以澄清这一点。同样,我的错,没有正确阅读该答案。
    • 需要明确的是,另一个答案中提出的解决方案绝对不会在并发模式下工作,因为不能保证isDisabled.current = true;提交.
    • 鉴于isDisabled.current = true 正在单击处理程序内部执行,它应该在任何setState 之前,因此在任何渲染阶段甚至开始之前,但反应可能会撤消它吗?我有点惊讶,即使useRef 获得的可变参考单元格没有被作为 ref 属性或类似属性传递,它的反应甚至会触及它们。
    • 何时调用 setState 或更改 ref 的值无关紧要,重要的是它的效果不会提交(并且在渲染调用中可见)直到下一次渲染完成而不被暂停。否则你会得到不一致的状态。想想useEffect:如果你允许setState 被提交但实际上不应用DOM 操作,那么useEffect 在下一次渲染后会看到什么版本的状态?更糟糕的是,您未更新的 DOM 中可能存在的 ref 呢?
    【解决方案2】:

    据我所知,setState 调用始终是异步的,而且您永远无法保证按钮在单击后立即被禁用。 JS 中也没有 concurrency 这样的东西,它有单线程,问题是渲染可能比你预期的要晚,所以你可以再次点击,直到 React 为你重新渲染。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-26
      • 2012-03-22
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 2018-06-08
      • 1970-01-01
      • 2013-04-07
      相关资源
      最近更新 更多