使用 React 创建的表单不会更新支持状态对象

Form created with React doesn't update the backing state object

本文关键字:更新 支持 状态 对象 React 创建 表单 使用      更新时间:2023-09-26

我正在尝试自动化Instagram网络应用程序的登录表单:

https://instagram.com/accounts/login/

使用以下代码(您可以在 Chrome 控制台上运行它):

var frm = window.frames[1].document.forms[0];
frm.elements[0].value = 'qacitester';
frm.elements[1].value = 'qatester';
frm.elements[2].click();

即使填充了输入,当我监视 XHR 请求时,我看到以下内容已发布:

用户名=&

密码=&意图=

而不是这个:

用户名=qacitester&password=qatest&intent=

并导致 Web 应用无法进行身份验证。你知道为什么输入值没有转移到 React 的支持状态(模型?)对象吗?

Instagram正在使用ReactLink的linkState方法:

http://facebook.github.io/react/docs/two-way-binding-helpers.html

登录页面链接到包含压缩代码的bundles/Login.js,如下所示:

o.createElement("input", {
    className:"lfFieldInput",
    type:"text",
    name:"username",
    maxLength:30,
    autoCapitalize:!1,
    autoCorrect:!1,
    valueLink:this.linkState("username")
}))

如果您查看 ReactLink 的文档,这意味着输入字段值只有在输入字段上触发更改事件时才会发送回状态。但是,这并不像在相关节点上触发更改事件那么简单 - 我认为是由于 React 规范化浏览器事件的方式。React 文档特别指出,由于各种原因,React 中的 onChange 与浏览器中的 onChange 并不完全相同:

http://facebook.github.io/react/docs/dom-differences.html

幸运的是,在您的情况下,Instagram正在使用包含插件的React,这使您可以访问React TestUtils,因此您可以执行以下操作:

var frm = window.frames[1].document.forms[0];
var fReact = window.frames[1].React;
fReact.addons.TestUtils.Simulate.change(frm.elements[0], {target: {value: 'qacitester' }});
fReact.addons.TestUtils.Simulate.change(frm.elements[1], {target: {value: 'qatester' }});
frm.elements[2].click();

更多文档在这里:

http://facebook.github.io/react/docs/test-utils.html

请注意,由于登录表单本身位于 iframe 中,因此您必须使用该 iframe 中的 React 实例,否则 TestUtils 将无法正确找到节点。

仅适用于页面上包含 React 插件的情况。React 的正常非插件构建将需要另一种解决方案。但是,如果您专门谈论 ReactLink,那么无论如何这是一个插件,所以这不是问题。

回答我自己的问题,所需的事件似乎是输入事件,并且此代码有效:

var doc = window.frames[1].document;
var frm = doc.forms[0];
frm.elements[0].value = 'qacitester';
var evt = doc.createEvent('UIEvent');
evt.initUIEvent('input', true, false);
frm.elements[0].dispatchEvent(evt);
frm.elements[1].value = 'qatester';
frm.elements[1].dispatchEvent(evt);
frm.elements[2].click();

登录表单实际上位于iFrame中,这使事情变得更加复杂。我能够从chrome控制台成功提交运行以下代码的登录名。我正在使用 React 的 TestUtils,但你可能对 AutoType 没问题:

// run in Chrome console
var frame = document.querySelector('iframe[src*="login"]');
var s = document.createElement('script');
var username = frame.contentDocument.querySelector('input[name="username"]');
var password = frame.contentDocument.querySelector('input[name="password"]');
s.src = "https://fb.me/react-with-addons-0.12.0.js";
frame.contentDocument.body.appendChild(s);
// wait for the script request to resolve and use:
change = frame.contentWindow.React.addons.TestUtils.Simulate.change;
change(username, {target: {value: 'My Great Username'}});
change(password, {target: {value: 'securepassword'}});

背景

React 使用自己的虚拟事件系统,并且通常使用受控组件,这本质上意味着输入value由 react 设置并向下渗透到 DOM 元素。

考虑到这一点,我相信您遇到的问题是因为 react 在输入上查找关键事件,并使用事件发生时的输入值来设置 DOM 元素的值。由于您只是设置 DOM 元素的 value 属性,因此您正在打破受控组件循环。

我做了一个快速的峰值,试图在输入上调度一个KeyboardEvent,但唉,它没有成功。如果我确实成功了,我会更新我的答案。

不令人满意的解决方法:

使用 casper/phantomjs 自动填写表单。这意味着您不必担心自己调度事件,您可以让"浏览器"为您完成。这也比在控制台中手动工作要好。