为什么在dispatchEvent时React事件处理程序没有被调用?

30

考虑一个React组件中的以下输入元素:

<input onChange={() => console.log('onChange')} ... />

在测试React组件时,我模拟用户更改输入值:

input.value = newValue;
TestUtils.Simulate.change(input);

预期的结果是记录'onChange',这个问题得到了解决。

然而,当直接分发'change'事件时(我正在使用jsdom):

input.value = newValue;
input.dispatchEvent(new Event('change'));

onChange 处理程序未被调用。

为什么?

我选择使用 dispatchEvent 而非 TestUtils.Simulate 的动机是因为 TestUtils.Simulate 不支持事件冒泡,而我的组件行为依赖于它。 我想知道是否有一种方法可以在没有 TestUtils.Simulate 的情况下测试事件?

4个回答

20

React使用自己的事件系统,使用SyntheticEvents来避免浏览器的不兼容性,并使React对事件有更多的控制。

正确使用TestUtils可以创建这样的事件,从而触发您的onChange监听器。

另一方面,dispatchEvent函数将创建一个“本地”浏览器事件。但是,您拥有的事件处理程序是由React管理的,因此只会对React的SyntheticEvents做出反应。

您可以在此处阅读有关React事件的更多信息:https://facebook.github.io/react/docs/events.html


1
love the badumts ha - HaveAGuess

19

不使用ReactTestUtils.Simulate的一种方法:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://unpkg.com/react-trigger-change/dist/react-trigger-change.js';
document.head.appendChild(script);
input.value = value;
reactTriggerChange(input);

查看react-trigger-change的源代码,只挑选需要的部分。示例代码片段:

if (nodeName === 'select' ||
    (nodeName === 'input' && type === 'file')) {
    // IE9-IE11, non-IE
    // Dispatch change.
    event = document.createEvent('HTMLEvents');
    event.initEvent('change', true, false);
    node.dispatchEvent(event);
  }

1
you are my savior! - shlensky
这里的信息不够充分。value是什么?如果我想要点击一个按钮,应该添加到const input还是const input.value?所以我尝试将其设置为const input,然后得到了reactTriggerChange未定义的错误,所以我添加了一个导入语句。然后出现了无法读取未定义的'toLowerCase'属性的错误。此外,我有很多需要清理才能使用此包的linting错误:(。 - vapcguy
1
@rofrol,我认为我的问题在于我获取“node”的方式。当我分配了一个ID并使用const node = document.getElementById("myID")时,我最终让TestUtils.Simulate.click(node);起作用了。我再次尝试使用上面使用initEventdispatchEvent的代码,它不再给我报错,但是它也没有起作用——什么都没有发生。React版本15.3.1。我不知道。 - vapcguy
React在文档级别处理事件,这就是为什么你的答案有效的根本原因。因此,您无需切换到已弃用的initEvent语法 - 只需将{bubbles: true}作为事件构造函数的第二个参数添加到原始代码中即可。 - James Pearce
@JamesPearce 谢谢。你能编辑答案添加新的方法吗? - rofrol
显示剩余2条评论

3

我创建了一个小版本的https://github.com/vitalyq/react-trigger-change,仅适用于输入元素。

无需外部依赖项,复制粘贴即可使用。

适用于React 16.9,在Safari(14)、Chrome(87)和Firefox(72)上进行了检查。

const triggerInputChange = (node: HTMLInputElement, inputValue: string) => {
      const descriptor = Object.getOwnPropertyDescriptor(node, 'value');

      node.value = `${inputValue}#`;
      if (descriptor && descriptor.configurable) {
        delete node.value;
      }
      node.value = inputValue;

      const e = document.createEvent('HTMLEvents');
      e.initEvent('change', true, false);
      node.dispatchEvent(e);

      if (descriptor) {
        Object.defineProperty(node, 'value', descriptor);
      }
};

<input type='checkbox' /> 怎么样?我把 value 替换成了 'checked',但是不起作用。 - Heyyy Marco
这个答案有一个更短的版本,代码更少:https://dev59.com/6WAg5IYBdhLWcg3wLYaI#46012210 - ge333

2
关键是在创建事件时添加{ bubbles: true }。例如:
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接