错误:react-dom@17.x事件委派问题

aiqt4smr  于 2022-10-28  发布在  React
关注(0)|答案(9)|浏览(186)

这几乎是TanStack/table#3002的副本
React版本:17.x版本

错误描述

在开发过程中,我忘记了在传递到react-table的useTable钩子之前记住一些模拟的数据,并遇到了一个有趣的bug,它让我陷入了兔子洞,而不是真正看到问题(我的数据上缺少useMemo)
仅当您将useTableuseSortBy组合使用时才会发生此问题,并且仅当与react-dom@17.x(事件委派重写版本)组合使用时才会发生此问题,例如react-dom@16.x可以正常工作。
这很难用语言来表达,但我附上了一个最小的例子(codesandbox),并将尽我所能来解释。在codesandbox中,我有三个组件:

  • Modal,它有一个useEffect,它为document添加了一个单击事件侦听器。这个侦听器的作用就像一个背景(如果我单击背景,“模式”应该关闭。
  • Table是调用useTable的组件(但不使用它返回的任何内容)-也由应用程序呈现。
  • App,它是主应用程序,控制着决定是否渲染Modal的状态。这里我主动“忘记”在我的数据上使用useMemo

发生的情况是,当您使用react-dom@17.x单击“Open Modal”时,将调用对document.addEventListener('click', ..)的回调,但在触发按钮的click事件时,该元素还不应该呈现。请参阅“modal”。使用react-dom@16.x(您可以在沙箱中更改)做同样的操作会导致实际显示“modal”的预期行为。

代码与框示例

https://codesandbox.io/s/react-table-react-17-5nw4d?file=/src/App.js

重现步骤

重现该行为的步骤:
1.转到https://codesandbox.io/s/react-table-react-17-5nw4d?file=/src/App.js
1.查看react-dom版本是17.0.1
1.按下“打开模态”按钮
1.没有任何React
1.例如,将react-dom的版本更改为16.14.0。
1.按下“打开模态”按钮
1.看到模态按预期打开。

预期行为

显然我不能期望react-table库能正常工作,但是我不期望它有奇怪的未定义行为,我也不期望它在react-dom版本中有不同的行为,即使它是一个主要的版本。

其他上下文

我非常好奇到底发生了什么,这是react-dom还是react-table的bug,还是两者的结合。我一直在调试,试图找到根本原因,但不幸的是,我对这两个repos的代码都不是很熟悉,我相信有些事情是异步发生的,这对我来说很难理解。
如果有人愿意帮助我调试这个问题,那就太好了,这样我们就可以找到问题的根源,也许还有一个更简单的例子。

i5desfxk

i5desfxk1#

我不认为这里有什么错误。您可以使用vanilla DOM重现相同的行为:
https://codesandbox.io/s/immutable-wildflower-w82nx?file=/src/index.js的最大值

document.getElementById("app").innerHTML = `
<button id="btn">
click me
</button>
`;
document.getElementById("btn").onclick = function () {
  document.addEventListener("click", function () {
    alert("document click fired");
  });
};

如果您在按钮单击 * 期间 * 添加文档侦听器,则该侦听器将触发(作为向上传播的单击的一部分)。
这在React〈17中没有发生,因为React习惯于在文档级注册它的事件。因此React处理程序运行得太晚(当事件已经冒泡到文档级时),因此注册另一个文档级侦听器不会有任何后果。但是React 17的行为更接近于常规DOM的工作方式。

uujelgoq

uujelgoq2#

您是否考虑过使用https://reach.tech/dialog之类的东西,而不是自己滚动?

jc3wubiy

jc3wubiy3#

Hi @gaearon,谢谢你花时间帮我弄清楚这个问题。是的,我在发行说明中看到监听器被移到了应用程序根目录,而不是保持在文档级别,所以我希望事情会出现泡沫(我知道我可以通过停止传播来解决这个问题),但对我来说奇怪的是,在我的脑海中,Modal中的效果应该还没有运行。这种行为只在useSortBy被传递给useTable时发生,否则传播会像我所期望的那样工作。
在我看来,它看起来像:
1.在第33行调用setOpen时,单击处理程序在setOpen之后立即返回,然后,如果要处理事件循环中的任何其他任务,则希望单击事件继续向上冒泡,并且首先在该进程完成之后冒泡。
1.第1步的setOpenApp组件的重新呈现排入队列,因此它将再次运行,现在open实际上设置为true
1.此时,将挂载Modal并呈现一个包含“my component”内容的div
1.然后运行ModaluseEffect,调用document.addEventListener
1.在我看来,到了这个阶段,click-event早就消失了,并且已经被处理和传播了,但是我的document.addEventListener('click', ...)处理程序被调用,导致handleClick被运行,这反过来又执行setOpen(false)并立即隐藏模态
也许有一些异步的东西正在进行,我错过了。我正在使用Chrome调试器,看到我的addEventListener调用的处理程序被立即调用,让我认为事件传播被延迟了。
要继续您的普通示例,请执行以下操作:(在此处运行:https://codesandbox.io/s/falling-tdd-0tthx?file=/src/index.tsx):

document.getElementById("app").innerHTML = `
<button id="btn">
  click me
</button>
`;
document.getElementById("btn").onclick = function () {
  setTimeout(() => { // mimic async behavior of setOpen
    document.addEventListener("click", function () {
      alert("document click fired");
    });
  }, 0);
};

如果我们向onclick添加一个setTimeout,模仿使用useState的setter时的异步操作react,事件侦听器将不会立即触发,因为事件冒泡将在事件循环的下一个任务被拾取之前完成。再次,我在这里有点如履薄冰,也许我做了一些错误的假设。
关于库,我不认为我需要它在我的情况下(我使用一个小的小useBackdrop挂钩),如果我只是添加一个event.stopPropagation()到我的挂钩,我都很好,但我的好奇心是越来越好我-在我看来,这不应该是需要摆在首位。
编辑:当然,这一切都是基于setStateconst [state, setState] = useState(...)是异步的,我猜想对于fiber,这一点并不总是正确的。(又一次如履薄冰)但即使如此,有趣的是为什么只有当我传递useSortBy时才会发生这种情况。我测试了添加useFilters来代替,同样的情况也是如此,所以可能它不是真正的“插件”,但是当一个插件被添加到useTable

gpnt7bae

gpnt7bae4#

这是一个原始代码andbox的分支,没有将useSortBy传递给useTable钩子(但是实际上整个Table组件现在都可以删除,因为它没有任何作用)--正如你所看到的,它工作得很好。所以,看看useSortBy中的什么导致了click事件的传播被延迟是很有趣的。
https://codesandbox.io/s/react-table-react-17-forked-212ou?file=/src/App.js的最大值

nle07wnf

nle07wnf5#

17.x中的光纤渲染方式是否有任何更改?
也许这一切都可以归结为setOpen之后的同步与异步渲染-如果在React 16中是异步的,但在17.x中它被优化为同步,那么这就解释了行为上的差异。我找不到任何关于这一点的发布说明,所以我不确定这个副作用是被考虑到了还是实际上是一些意外的副作用。

ioekq8ef

ioekq8ef6#

好的谢谢。
2021年1月25日星期一上午11:29 Oscar Linde***@***.***〉写道:在17.x中,Fiber的渲染方式有什么变化吗?也许这一切都归结为setOpen之后的同步与异步渲染--如果在React 16中是异步的,但在17.x中它被优化为同步,那么这就解释了行为上的差异。我找不到任何关于这方面的发行说明,所以我不确定这个副作用是否是考虑到的东西,或者如果它实际上是一些意想不到的副作用. -你收到这个,因为你订阅了这个线程.直接回复这个电子邮件,在GitHub〈#20636(comment)〉上查看它,或者取消订阅< https://github.com/notifications/unsubscribe-auth/APYPGPVLSTRTWRTURPQVYVDS3VI2NANCNFSM4WNIAS2A >。

klsxnrf1

klsxnrf17#

在迁移到react@17.x后,我们遇到了类似的问题。
它是否期望react@17中的行为(特性),或者它是一个bug?在我的想法中,当我们改变依赖关系或只是安装它时,useEffect应该被异步调用,但实际上它有时被同步调用,这导致了这样的 * 讨厌 * 问题。

14ifxucb

14ifxucb8#

在我的想法中,当我们改变依赖关系或只是安装它时,useEffect应该被异步调用,但在现实中,有时它被同步调用,这导致了这样的 * 讨厌 * 问题。
相关:#20863(评论)

7vux5j2d

7vux5j2d9#

React 17将事件委派目标的根从document更改为AppRoot,但我仍然可以使用此codesandbox重现此问题。
在React组件中,这里的事件冒泡与DOM实现有些不一致,当用户尝试进行精细的事件冒泡操作时,应该注意这一点。

相关问题