在 response js 中触发 onchange 事件的最佳方式是什么

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

现在我们面临着一个问题:

我们有 jquery.mask.js插件,它以编程方式格式化输入值,因此它不会触发反应事件。当模型从用户输入接收到 未格式化值,从插件接收到 没有格式化的值时,所有这些都会导致这种情况。

React 似乎有很多依赖于浏览器的事件处理策略。对于特定的 DOM 元素,是否有任何通用的方法来触发变更事件,以便 React 能够听到它?

202390 次浏览

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

在这种情况下,我建议不要使用 valueLink,而只是监听插件发出的更改事件,并更新输入的状态作为响应。双向绑定更多的是作为一个演示,而不是其他任何东西; 它们包含在插件中只是为了强调这样一个事实: 纯双向绑定不适合大多数应用程序,你通常需要更多的应用程序逻辑来描述应用程序中的交互。

如果您正在使用 Backbone 和 React,我建议您使用以下方法之一,

它们都有助于将 Backbone 模型和集合与 React 视图集成在一起。您可以像使用 Backbone 视图一样使用 Backbone 事件。我已经涉足了这两个领域,并没有看到太多的区别,除了一个是混合,另一个改变 React.createClassReact.createBackboneClass

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

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

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

这触发了 domNode 上的 click 事件,我通过 response 连接的处理程序确实被调用了,因此它的行为就像有人单击元素时我所期望的那样。我还没有在 Change 上测试过,但是它应该可以工作,而且不确定这在真正的旧版本 IE 中是否公平,但是我相信 MouseEvent 至少在 IE9及以上版本中是支持的。

对于我的特定用例,我最终放弃了这种做法,因为我的组件非常小(由于我还在学习它,所以只使用了应用程序的一部分) ,而且我可以通过另一种方式实现同样的事情,而不需要获得对 dom 节点的引用。

更新:

正如其他人在评论中所说的,最好使用 this.refs.refname获得对 dom 节点的引用。在这种情况下,refname 是通过 <MyComponent ref='refname' />附加到组件的引用。

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

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

在任意元素上触发更改事件会在组件之间产生依赖关系,这是很难推断的。最好坚持 React 的单向数据流。

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

我已经构建了完成这个任务的 反应-触发-改变,但它旨在用于测试,而不是作为一个生产依赖项:

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


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

CodePen

因为我们使用函数来处理 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)} />
);
}
}

对于反应16和反应 > = 15.6

Setter.value=不能正常工作,因为 React 库覆盖了输入值 Setter,但是我们可以直接在 input上调用函数作为上下文。

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 元素,应该使用 HTMLTextAreaElement类的 prototype

新的 代码的例子

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

过时的答案只适用于 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);

我做了个 举例说明

为了理解为什么需要新的旗帜,我发现 此评论非常有帮助:

React 中的输入逻辑现在可以忽略变更事件,这样它们就不会触发 每个值不止一次。它监听两个浏览器 onChange/onInput 事件以及 DOM 节点值支撑上的集合(当您更新 这有副作用的意思是,如果你 手动更新输入值 input.value = ‘ foo’,然后分派一个 具有{ target: input } React 的 ChangeEvent 将同时注册这两个集合 事件的值仍然是“ foo”就当它是重复的 然后吞下去。

这在正常情况下工作良好,因为“真正的”浏览器启动 事件不会触发 element.value 上的设置 这个逻辑秘密地通过标记事件,你触发了一个模拟 旗帜和反应将始终火灾事件。 Https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventplugins/changeeventplugin.js#l128

扩展了 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);


}


};

我在 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)
}
}

对于 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);

事件类型 input没有为我工作的 <select>,但改变它为 change的工作

useEffect(() => {
var event = new Event('change', { bubbles: true });
selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);

这个丑陋的解决方案对我有效:

let ev = new CustomEvent('change', { bubbles: true });
Object.defineProperty(ev, 'target', {writable: false, value: inpt });
Object.defineProperty(ev, 'currentTarget', {writable: false, value: inpt });
const rHandle = Object.keys(inpt).find(k => k.startsWith("__reactEventHandlers"))
inpt[rHandle].onChange(ev);

我今天偶然发现了同样的问题。虽然在 JavaScript 中默认支持开箱即用的 'click', 'focus', 'blur'事件,但是还没有实现其他有用的事件,比如 'change', 'input'

我想出了这个通用的解决方案,并根据已接受的答案重构了代码。

export const triggerNativeEventFor = (elm, { event, ...valueObj }) => {
if (!(elm instanceof Element)) {
throw new Error(`Expected an Element but received ${elm} instead!`);
}


const [prop, value] = Object.entries(valueObj)[0] ?? [];
const desc = Object.getOwnPropertyDescriptor(elm.__proto__, prop);


desc?.set?.call(elm, value);
elm.dispatchEvent(new Event(event, { bubbles: true }));
};

它是怎么工作的?

triggerNativeEventFor(inputRef.current, { event: 'input', value: '' });

'event'键值对之后传递的任何第二个属性都将被考虑,其余的将被忽略/丢弃。 为了避免帮助函数的参数定义混乱,特意编写成这样。 为什么没有默认为 'value'获取描述符的原因是,例如,如果您有一个本机复选框 <input type="checkbox" />,那么它没有一个值,而不是一个 'checked'道具/属性。然后,您可以按如下方式传递所需的检查状态:

triggerNativeEventFor(checkBoxRef.current, { event: 'input', checked: false });