最近在对 hiagent 进行二次开发时,我就遇到了这个棘手的问题。经过深入研究和多次尝试,终于找到了完美的解决方案。今天就来分享这个"黑科技"!
🎯 终极解决方案
废话不多说,先上最终的解决方案:
javascript
function changeValueOfInput(dom, value) {
const nativeValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
nativeValueSetter.call(dom, value);
dom.dispatchEvent(new Event("input", { bubbles: true }));
}
这短短几行代码,就能完美解决 React 受控组件的输入问题!
🤔 为什么普通方法不行?
问题的根源
你可能会问:为什么简单的 dom.value = "新值"
不起作用呢?
核心原因:React 受控组件的机制
在 React 中,受控组件的值完全由 state 控制。当你直接设置 input.value
时:
-
❌ 只改变了 DOM 的 value 属性
-
❌ 没有触发
onChange
事件 -
❌ 组件的 state 没有更新
-
❌ 下次 render 时,输入框的值又恢复到 state 的值
React 的"拦截机制"
更深层的原因是,React 为了实现受控组件,重写了 input 元素的 setter。当你尝试设置 input.value
时,React 的 setter 会阻止这个操作!
💡 解决方案详解
第一步:获取原生 DOM 的 value setter
javascript
const nativeValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
这行代码的作用是绕过 React 重写的 setter,直接获取浏览器原生的 value setter。
第二步:使用原生 setter 设置值
javascript
nativeValueSetter.call(dom, value);
通过 call
方法,我们使用原生的 setter 来设置 input 的值,成功绕过了 React 的拦截。
第三步:触发 input 事件
javascript
dom.dispatchEvent(new Event("input", { bubbles: true }));
仅仅设置值还不够,我们需要手动触发 input
事件,让 React 的 onChange
处理函数执行,从而更新组件的 state。
🧪 完整的测试 Demo
我准备了一个完整的 CodeSandbox 示例,你可以直接体验效果:
👉 在线演示地址
typescript
import { useState, useEffect } from "react";
const App = () => {
const [value, setValue] = useState("init");
const [flag, setFlag] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setFlag((v) => v + 1);
}, 2000);
return () => clearInterval(timer);
}, []);
return (
<div>
<div>input 为受控组件</div>
<input
value={value}
onchange={(e) => {
console.log("change", e.target.value);
setValue(e.target.value);
}}
/>
<button onclick={() => setFlag((v) => v + 1)}>
强制 render {flag}
</button>
<div>当前值: {value}</div>
</div>
);
};
export default App;
// 🚀 1秒后自动填充输入框
setTimeout(() => {
changeValueOfInput(document.querySelector("input"), "hello world!");
}, 1000);
// ✅ 最终解决方案
function changeValueOfInput(dom, value) {
const nativeValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
nativeValueSetter.call(dom, value);
dom.dispatchEvent(new Event("input", { bubbles: true }));
}
🚫 我尝试过的失败方案
在找到最终解决方案之前,我尝试了多种方法,都以失败告终:
方案1:直接修改 value
javascript
// 失败:只改变了 dom value, 但是为受控组件,rerender 后就复原了。
function changeValueOfInput(dom, value) {
dom.value = value;
}
方案2:修改 value + 触发事件
javascript
// 失败:没有触发 react onChange
function changeValueOfInput(dom, value) {
dom.value = value;
dom.dispatchEvent(new Event("input", { bubbles: true }));
// 也不行
// dom.dispatchEvent(new Event("change", { bubbles: true }));
}
方案3:模拟键盘输入
javascript
// 失败:没有触发 react onChange
function changeValueOfInput(dom, value) {
dom.focus();
value.split("").forEach((k) => {
console.log("k", k);
dom.dispatchEvent(new InputEvent("input", { data: k, bubbles: true }));
// dom.dispatchEvent(new KeyboardEvent("keyd", { key: k, bubbles: true }));
});
}
方案4:使用 execCommand
javascript
// 失败:没有效果,因为需要用户参与的事件才行
function changeValueOfInput(dom, value) {
dom.focus();
document.execCommand("paste");
}
⚠️ 使用注意事项
虽然这个方案有效,但需要注意以下几点:
1 这个方法绕过了 React 的正常机制,本质上是一种 hack。
2 可能与某些第三方库产生冲突。
3 在生产环境中使用时需要谨慎评估。
🎉 总结
通过获取原生 DOM 的 value setter 并手动触发 input 事件,我们成功解决了 JavaScript 模拟用户输入在 React 受控组件中的问题。
这个方案的核心思路是:
-
🎯 绕过 React 的 setter 拦截,🔧 使用原生 DOM API 设置值
-
📡 手动触发事件更新 state
虽然是一种 hack 手段,但在特定场景下确实非常有用。希望这篇文章能帮助到遇到类似问题的朋友们!
如果觉得这篇文章对你有帮助,别忘了点赞和转发哦! 👍