Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js如何在外部改变react受控组件的状态量? #22

Open
ILovePing opened this issue Jun 19, 2020 · 5 comments
Open

js如何在外部改变react受控组件的状态量? #22

ILovePing opened this issue Jun 19, 2020 · 5 comments
Labels
WIP work in progress

Comments

@ILovePing
Copy link
Owner

ILovePing commented Jun 19, 2020

楔子:

最近有个同学做chrome插件,可以自动触发预设的一系列元素事件,比如小明通过点击开始百度搜索游戏机按钮,该插件就可以直接给百度搜索输入框填入游戏机然后自动回车搜索。需求提炼出来其实就是自动化触发前端页面dom元素的一系列事件,主要分为两类:

  1. 键盘事件,比如input元素输入赋值,keyCode13回车事件
  2. 鼠标事件,比如click事件,mouseover事件,scroll滚动事件

开始思考

  1. 首先从最简单的触发,只考虑原生html/js开发的网站:
    input元素赋值很简单
$eventTarget.value="游戏机"

触发回车事件也很简单

$eventTarget.click()
// 或者
$eventTarget.dispatchEvent(new MouseEvent('click'));

触发hover事件,类似的,都可以通过Event创建事件来dispatch触发

$eventTarget.dispatchEvent(new Event("mouseover"))

触发页面滚动事件

document.documentElement.scrollTop = 100
  1. 然而对于不同mvvm框架(react/vue)开发的页面如何触发的实现都或多或少有点不同。
    比如对于react组件库,antd官方一个简单的form表单
    image
    当我们对username的输入框使用$eventTarget.value="游戏机"时,我们发现输入框内容确实变成了游戏机⬇️
    image
    image
    然而当我们点击submit按钮时发现输入框重新变为空了,而且表单校验也判断输入框的值为空,
    image
    也就是说通过一开始假想的赋值操作并没有改变通过react生成的input标签值。

上述例子可以用一个简单的受控组件复现:
image
image
当我们正常输入时第二行文字会正常反应组件内部state,当我们通过js给input元素value赋值为3时,我们发现input框内变成了3,而组件内部state其实并没有变为3,还是原来的值:
image
其实用点脑子想想也知道都受控组件了,直接修改dom value肯定不生效的啊?你当我react不存在吗?
那么尝试使用dispatchEvent去触发dom的赋值行为呢,会不会

。。
。。。
。。。。同样不行
凡事不会先google,在react官方issue中找到一个方法:

let input = someInput; 
let lastValue = input.value;
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(lastValue);
}
input.dispatchEvent(event);
@ILovePing ILovePing added the WIP work in progress label Jun 19, 2020
@ILovePing
Copy link
Owner Author

ILovePing commented Jun 26, 2020

可以看到这段代码最后也是dispatch一个冒泡阶段的事件(尝试把bubbles设置为false则不触发,为什么必须是冒泡阶段?这是疑问1⃣️)。
Besides,对于15和16版本有不同的hack方法,这两种hack方法分别对应react-dom内部input元素渲染的两种机制分别是怎样的,这个simulated和valueTracker干嘛的?这是疑问2⃣️。

带着这两个疑问开始翻看源码探究:
一个input标签的渲染:

ReactDOM.render(
<Input type="text"  
  value={state} 
  onClick={e => setState(e.target.value)}
/>,
document.getElementById("root"))

查看源码ReactDOM.render的实现:

@LinkTsang
Copy link

写油猴脚本遇到了类似的问题,同样从 react issue 过来的,翻了react的源码没找到 event.simulated的定义(这是个待填坑吗哈哈

@ILovePing
Copy link
Owner Author

@LinkDoyle 确实是todo的坑,我去16源码看了一遍,想要把整个事件绑定到触发的流程描述出来,奈何描述能力太差 😂 ,后来写业务代码就搁置了;btw,simulated在15版本的react源码中能找到实现。

@ILovePing ILovePing changed the title react和vue事件机制分析 js如何在外部改变react受控组件的状态量? Feb 15, 2021
@leoxiaoge
Copy link

leoxiaoge commented May 2, 2021

通过js在外部改变React受控组件的状态量

let input = document.getElementsByClassName("input");
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(input);
}
input.dispatchEvent(event);

@Sshrimp
Copy link

Sshrimp commented Nov 1, 2021

所以想问下,类似于ANTD中的select组件,有办法从外部JS改变值吗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WIP work in progress
Projects
None yet
Development

No branches or pull requests

4 participants