在React JS中触发onchange事件的最佳方法是什么


112

我们使用Backbone + ReactJS捆绑包来构建客户端应用程序。严重依赖臭名昭著的是,valueLink我们通过自己的支持ReactJS接口进行双向绑定的包装器将值直接传播到模型。

现在我们面对这个问题:

我们有一个jquery.mask.js插件,可以以编程方式格式化输入值,因此不会触发React事件。当模型从用户输入中接收未格式化的值而从插件中丢失格式化的值时,所有这些都会导致情况。

似乎React依赖于浏览器有很多事件处理策略。有什么通用的方法可以触发特定DOM元素的更改事件,以便React可以听到吗?


👉React Github讨论:触发React 16的模拟输入值更改
vsync

Answers:


174

对于React 16和React> = 15.6

设置.value=器无法正常工作,因为React库会覆盖输入值设置器,但我们可以直接在inputas上下文中调用该函数。

var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');

var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);

对于textarea元素,你应该使用prototypeHTMLTextAreaElement类。

新的codepen示例

所有学分这个贡献者他的解决方案

仅适用于React <= 15.5的过时答案

有了react-dom ^15.6.0您可以使用simulated标志事件对象的事件经过

var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);

用一个例子做了一个codepen

为了理解为什么需要新标记,我发现此注释非常有帮助:

现在,React中的输入逻辑对重复事件的重复事件进行重复数据删除,因此每个值不会触发多次。它侦听浏览器的onChange / onInput事件以及DOM节点值prop上的设置(当您通过javascript更新值时)。这具有副作用,即如果您手动更新输入的值,则input.value ='foo'然后使用{target:input}分派一个ChangeEvent React将同时注册集合和事件,看到它的值仍然是''foo ',将其视为重复事件并吞下。

在正常情况下,这可以正常工作,因为“真实的”浏览器启动的事件不会触发element.value上的设置。您可以通过使用模拟标志标记触发的事件来暗中逃避此逻辑,并且反应将始终触发该事件。 https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128


1
谢谢,这确实从react-dom ^ 15.6.0开始。但是似乎在React 16.0之后这已经停止工作了。除了使用模拟标志来触发变更事件之外,还有其他想法吗?
Qwerty

相信这里有个要提示的地方:reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes知道这可能是什么吗?
Qwerty

@Qwerty我更新了答案,可能对您有用
Grin

以及什么onclick按钮?应该采取什么措施来触发此类事件?
Bender

@Bender,您只需在元素上调用本机click方法
Grin

63

至少在文本输入上,似乎onChange正在监听输入事件:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);

取决于浏览器版本。IE8不支持输入事件。当您从文本框中删除字符时,ie9不会触发输入事件。developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
React 不再支持 IE8 。对于IE9,您也许可以使用var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });,但我没有IE9 VM。
迈克尔

@Michael我正在IE11上尝试您的代码,它不适用于reactjs输入字段。它适用于普通的HTML输入。它也适用于Edge。var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Bodosko

19

我知道这个答案来得有点晚,但最近我遇到了类似的问题。我想在嵌套组件上触发事件。我有一个包含单选和复选框类型的小部件的列表(它们是div,其行为类似于复选框和/或单选按钮),在应用程序的其他位置,如果有人关闭了工具箱,则需要取消选中一个。

我找到了一个非常简单的解决方案,不确定这是否是最佳做法,但它是否有效。

var event = new MouseEvent('click', {
 'view': window, 
 'bubbles': true, 
 'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);

这触发了domNode上的click事件,并且确实调用了我通过react连接的处理程序,因此它的行为就像我期望有人单击该元素一样。我尚未测试过onChange,但是它应该可以工作,并且不确定在真正的旧版本IE中如何使用,但是我相信至少在IE9及更高版本中支持MouseEvent。

我最终在我的特定用例中放弃了这一点,因为我的组件很小(因为我仍在学习它,所以我的应用程序只有一部分使用了react),并且我可以通过另一种方式实现相同的目的而无需获取dom节点的引用。

更新:

正如其他人在评论中指出的那样,最好使用它this.refs.refname来引用dom节点。在这种情况下,refname是您通过附加到组件的ref <MyComponent ref='refname' />


1
您可以使用该React.findDOMNode功能代替ID 。goo.gl/RqccrA
m93a 2015年

2
>您可以使用React.findDOMNode函数代替ID。或将a添加ref到您的元素中,然后使用this.ref.refName.dispatchEvent
silkAdmin 2015年

此后,框架很可能已更改,其this.ref s .refname
nclord

1
字符串引用现在被认为是旧式的,将来将不推荐使用。回调引用是跟踪DOM节点的首选方法。
dzv3

9

您可以使用ReactTestUtils模拟事件,但这是为单元测试而设计的。

我建议在这种情况下不使用valueLink,而只是侦听插件触发的更改事件并作为响应更新输入的状态。双向绑定实用程序更像是一个演示程序;它们包含在附加组件中只是为了强调以下事实:纯双向绑定不适用于大多数应用程序,并且您通常需要更多的应用程序逻辑来描述应用程序中的交互。


我担心答案会是这样的。(问题是React对发送/接收工作流程过于严格。特别是必须指定onChange处理程序才能对用户输入做出反应,除了非受控状态外,别无其他方法在我的情况下,我应该只声明遵循该规则的处理程序,而相关输入将来自由jquery触发的onchange事件
。React

1
还有...如果您想使用ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin '16

8

扩展了Grin / Dan Abramov的答案,该方法适用于多种输入类型。在React> = 15.5中测试

const inputTypes = [
    window.HTMLInputElement,
    window.HTMLSelectElement,
    window.HTMLTextAreaElement,
];

export const triggerInputChange = (node, value = '') => {

    // only process the change on elements we know have a value setter in their constructor
    if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {

        const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
        const event = new Event('input', { bubbles: true });

        setValue.call(node, value);
        node.dispatchEvent(event);

    }

};

5
对选择元素不起作用。您需要为事件使用“更改”而不是“输入”。
styks

3

在任意元素上触发更改事件会在组件之间创建依赖关系,而这些依赖关系很难推理。最好坚持使用React的单向数据流。

没有简单的代码片段可以触发React的change事件。该逻辑在ChangeEventPlugin.js中实现,并且针对不同的输入类型和浏览器有不同的代码分支。此外,实现细节在不同版本的React中有所不同。

我已经构建了可以执行此操作的react-trigger-change,但它打算用于测试,而不是作为生产依赖项:

let node;
ReactDOM.render(
  <input
    onChange={() => console.log('changed')}
    ref={(input) => { node = input; }}
  />,
  mountNode
);

reactTriggerChange(node); // 'changed' is logged

密码笔


1

很好,因为我们使用函数来处理onchange事件,所以我们可以这样做:

class Form extends Component {
 constructor(props) {
  super(props);
  this.handlePasswordChange = this.handlePasswordChange.bind(this);
  this.state = { password: '' }
 }

 aForceChange() {
  // something happened and a passwordChange
  // needs to be triggered!!

  // simple, just call the onChange handler
  this.handlePasswordChange('my password');
 }

 handlePasswordChange(value) {
 // do something
 }

 render() {
  return (
   <input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
  );
 }
}

1

我在React的Github问题上发现了这一点:像魅力一样工作(v15.6.2)

这是我实现文本输入的方法:

changeInputValue = newValue => {

    const e = new Event('input', { bubbles: true })
    const input = document.querySelector('input[name=' + this.props.name + ']')
    console.log('input', input)
    this.setNativeValue(input, newValue)
    input.dispatchEvent(e)
  }

  setNativeValue (element, value) {
    const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
    const prototype = Object.getPrototypeOf(element)
    const prototypeValueSetter = Object.getOwnPropertyDescriptor(
      prototype,
      'value'
    ).set

    if (valueSetter && valueSetter !== prototypeValueSetter) {
      prototypeValueSetter.call(element, value)
    } else {
      valueSetter.call(element, value)
    }
  }

如果文本框值与您传递给setNativeValue的值相同,则此方法不起作用
Arun,

0

对于HTMLSelectElement,即<select>

var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
  window.HTMLSelectElement.prototype,
  "value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);

-1

如果您使用的是Backbone和React,建议您选择以下一种,

它们都有助于将Backbone模型和集合与React视图集成在一起。您可以像使用Backbone视图一样使用Backbone事件。我涉猎了两者,但没有发现太大的区别,只是一个是mixin,另一个React.createClassReact.createBackboneClass


请小心React与主干之间的“事件驱动”桥。First插件使用setProps而不考虑组件安装级别。错了 其次,严重依赖于forceUpdate并考虑到只能为一个组件分配一个模型,这是不常见的情况。此外,如果您在具有响应组件的复杂UI中共享模型,并且忘记了取消订阅导致forceUpdate的无用更新事件,那么您可以陷入递归渲染中,请注意这一点。
wallice
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.