如何以编程方式填充使用 React 构建的输入元素?

Posted

技术标签:

【中文标题】如何以编程方式填充使用 React 构建的输入元素?【英文标题】:How to programmatically fill input elements built with React? 【发布时间】:2017-04-15 03:30:57 【问题描述】:

我的任务是抓取使用 React 构建的网站。我正在尝试填写输入字段并使用 javascript 向页面注入表单(硒或移动设备中的 webview)。这在所有其他网站 + 技术上都像一个魅力,但 React 似乎是一个真正的痛苦。

所以这里是一个示例代码

var email = document.getElementById( 'email' );
email.value = 'example@mail.com';

我在 DOM 输入元素上的值发生了变化,但是 React 并没有触发 change 事件。

我一直在尝试多种不同的方法来让 React 更新状态。

var event = new Event('change',  bubbles: true );
email.dispatchEvent( event );

没用

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

不工作

email.onChange( event );

不工作

我不敢相信与 React 的交互变得如此困难。我将不胜感激任何帮助。

谢谢

【问题讨论】:

The value changes on the DOM input element, but the React does not trigger the change event. 您期待什么变化事件?如果您能够成功填写输入字段,那么为什么不document.forms[0].submit() 是的,这是我尝试做的——但是 React 验证器抱怨你必须填写值,即该值不会传播到 React 组件 啊,这很有趣——我明白了。我会折腾一个codepen并玩弄。 下面的答案帮助我克服了同样的问题,但我想知道您是否对提交表单的问题的后半部分有任何指导?我无法通过在表单的提交按钮上触发 .click() 或在表单元素本身上触发 .submit() 或通过使用下面建议的 dispatchEvent() 方法执行上述任一操作来成功提交反应表单。但是,手动单击提交按钮确实可以正确提交表单。我错过了什么? 【参考方案1】:

由于重复数据删除输入和更改事件的更改,此已接受的解决方案似乎不适用于 React > 15.6(包括 React 16)。

您可以在此处查看 React 讨论:https://github.com/facebook/react/issues/10135

这里建议的解决方法是: https://github.com/facebook/react/issues/10135#issuecomment-314441175

为方便起见,在此转载:

代替

input.value = 'foo';
input.dispatchEvent(new Event('input', bubbles: true));

你会使用

function 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(input, 'foo');
input.dispatchEvent(new Event('input',  bubbles: true ));

【讨论】:

这对我在greasemonkey 中不起作用——可能是关于安全性的问题......即使只是试图打电话给Object.getOwnPropertyDescriptor(..) 也失败了。对我来说,使用 ***.com/a/52486921 中的 setNativeValue(..) 函数在greasemonkey 中工作。 如何更改它以使其仅用于单击?另外,React 似乎发生了变化,所以你现在需要发送一个“change”事件来代替 直接在控制台中运行而不是从插件运行时可以工作(Object.getOwnPropertyDescriptor(element, 'value').set; 失败)。 似乎对我有用,但 Typescript 编译器对此一点也不满意 :)【参考方案2】:

React 正在侦听文本字段的 input 事件。

你可以更改值并手动触发输入事件,react 的onChange handler 会触发:

class Form extends React.Component 
  constructor(props) 
    super(props)
    this.state = value: ''
  
  
  handleChange(e) 
    this.setState(value: e.target.value)
    console.log('State updated to ', e.target.value);
  
  
  render() 
    return (
      <div>
        <input
          id='textfield'
          value=this.state.value
          onChange=this.handleChange.bind(this)
        />
        <p>this.state.value</p>
      </div>      
    )
  


ReactDOM.render(
  <Form />,
  document.getElementById('app')
)

document.getElementById('textfield').value = 'foo'
const event = new Event('input',  bubbles: true )
document.getElementById('textfield').dispatchEvent(event)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id='app'></div>

【讨论】:

谢谢,超时似乎可以解决问题——我不太明白为什么会这样——但如果我在没有超时的情况下(来自 selenium)做同样的事情,那么 React 永远不会得到事件。不过,我确实在使用超时时遇到了一些问题,所以我必须想办法解决它。 超时不是必需的,我添加它只是为了使示例更清晰。请参阅不带超时的更新。唯一需要确保的是,改变输入值的代码是在 react 组件完全渲染后执行的。 当然我完全明白了 - 问题是从 Selenium 进程调用事件不会触发,除非超时 - 你的示例现在与我在最初问题中的示例几乎相同,所以我很确定 selenium 桥中有什么东西弄乱了 js 引擎 仅供参考const event = new Event('input', bubbles: true ) document.getElementById('textfield').dispatchEvent(event) 解决了对我来说很多调试的问题。谢谢。 请注意,如果您有select 字段,则需要调度change 事件而不是input 事件。【参考方案3】:

这是输入、选择、复选框等最简洁的解决方案(不仅适用于反应输入)

/**
 * See [Modify React Component's State using jQuery/Plain Javascript from Chrome Extension](https://***.com/q/41166005)
 * See https://github.com/facebook/react/issues/11488#issuecomment-347775628
 * See [How to programmatically fill input elements built with React?](https://***.com/q/40894637)
 * See https://github.com/facebook/react/issues/10135#issuecomment-401496776
 *
 * @param htmlInputElement | HTMLSelectElement el
 * @param string value
 */
function setNativeValue(el, value) 
  const previousValue = el.value;

  if (el.type === 'checkbox' || el.type === 'radio') 
    if ((!!value && !el.checked) || (!!!value && el.checked)) 
      el.click();
    
   else el.value = value;

  const tracker = el._valueTracker;
  if (tracker) 
    tracker.setValue(previousValue);
  

  // 'change' instead of 'input', see https://github.com/facebook/react/issues/11488#issuecomment-381590324
  el.dispatchEvent(new Event('change',  bubbles: true ));

用法:

setNativeValue(document.getElementById('name'), 'Your name');
document.getElementById('radio').click(); // or setNativeValue(document.getElementById('radio'), true)
document.getElementById('checkbox').click(); // or setNativeValue(document.getElementById('checkbox'), true)

【讨论】:

谢谢,这符合我对使用 ReactJS 的自动完成和组合框字段的需求。 :)【参考方案4】:

我注意到输入元素有一些属性,其名称类似于__reactEventHandlers$...,它具有一些功能,包括onChange

这有助于找到该功能并触发它

let getReactEventHandlers = (element) => 
    // the name of the attribute changes, so we find it using a match.
    // It's something like `element.__reactEventHandlers$...`
    let reactEventHandlersName = Object.keys(element)
       .filter(key => key.match('reactEventHandler'));
    return element[reactEventHandlersName];


let triggerReactOnChangeEvent = (element) => 
    let ev = new Event('change');
    // workaround to set the event target, because `ev.target = element` doesn't work
    Object.defineProperty(ev, 'target', writable: false, value: element);
    getReactEventHandlers(element).onChange(ev);


input.value = "some value";
triggerReactOnChangeEvent(input);

【讨论】:

天哪。谢谢你。其他解决方案均无效。【参考方案5】:

没有元素ID:

export default function SomeComponent() 
    const inputRef = useRef();
    const [address, setAddress] = useState("");
    const onAddressChange = (e) => 
        setAddress(e.target.value);
    
    const setAddressProgrammatically = (newValue) => 
        const event = new Event('change',  bubbles: true );
        const input = inputRef.current;
        if (input) 
            setAddress(newValue);
            input.value = newValue;
            input.dispatchEvent(event);
        
    
    return (
        ...
        <input ref=inputRef type="text" value=address onChange=onAddressChange/>
        ...
    );

【讨论】:

以上是关于如何以编程方式填充使用 React 构建的输入元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式启动/停止 Metro Bundler

以编程方式更改由标签值对对象填充的 Dojo Form Select 的选定选项

以编程方式控制 xml 元素的顺序

如何以编程方式将元素添加到 ConfigurationElementCollection?

在 React 中渲染动态输入字段

在 React 中以编程方式修改元素:在 CSS 规则之前