在 React 中模拟输入

news2024/9/25 22:51:14

需求 与 Bug

项目的 C# 桌面端使用 CefSharp 内嵌了一个三方网站,在外部实现了一个登录控件,外部登录后希望内嵌的三方网站自动登录,实现代码如下:

browser.ExecuteScriptAsync($"document.getElementsByName('username')[0].value = '{_login}'");
browser.ExecuteScriptAsync($"document.getElementsByName('password')[0].value = '{_pwd}'");
browser.ExecuteScriptAsync($"document.getElementsById('submitBtn').click()");

通过 CefSharp 提供的方法在外部执行脚本,将登录控件的值填充至网站,旧版中正常,三方网站改版后无法正常填充。

尝试解决

直接修改 input 的 value 值无效,通过控制台 sources 面板查看,发现网站用 React 重构:

Image

React 是状态驱动的视图库,直接修改元素的 value 值不会导致 React 应用状态的改变,我们需要让 React 感知到数据变化,即事件模拟:

const inputEl = document.querySelector('input');
inputEl.value = "changed";
inputEl.dispatchEvent(new Event('input', { bubbles: true, cancelable: false, composed: true }));

结果是页面显示已经改变:

Image

但内部的状态依然未变:

Image

查询资料找到一个 React Issues,有人在讨论中给出解决方案 Trigger simulated input value,代码如下:

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

问题解决。

知其然而知其所以然

上述问题激起了个人好奇心,尝试通过调试理解模拟输入事件无效的原因。

通过控制台 -> Elements -> Event Listeners 面板,找到 input 事件,查看已注册的事件处理程序:

Image

React 实现了自己的合成事件系统,几乎所有的事件统一注册在 React 应用根元素上,这里即是截图中的 root 元素,点击右侧链接定位至源码:

Image

所有事件都经过此函数,避免时间浪费,我们加上条件,只有 input 事件才会断点:

Image

输入触发断点,中间会经过很多我们不关心的流程,最终进入 updateValueIfChanged 函数:

function getTracker(node) {
  return node._valueTracker;
}

function getValueFromNode(node) {
  var value = '';

  if (!node) {
    return value;
  }

  if (isCheckable(node)) {
    value = node.checked ? 'true' : 'false';
  } else {
    value = node.value;
  }

  return value;
}

function updateValueIfChanged(node) {
  if (!node) {
    return false;
  }

  // 获取 tracker
  var tracker = getTracker(node);

  if (!tracker) {
    return true;
  }

  // 获取上次的值
  var lastValue = tracker.getValue();
  // 获取变更值
  var nextValue = getValueFromNode(node);

  // 不同则更新,此处是关键节点
  if (nextValue !== lastValue) {
    tracker.setValue(nextValue);
    return true;
  }

  return false;
}

tracker 的定义如下:

function trackValueOnNode(node) {
  // 判断 input 类型
  var valueField = isCheckable(node) ? 'checked' : 'value';

  // 定义属性描述符
  var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);

  // 设置初始值
  var currentValue = '' + node[valueField];

  if (
    node.hasOwnProperty(valueField) ||
    typeof descriptor === 'undefined' ||
    typeof descriptor.get !== 'function' ||
    typeof descriptor.set !== 'function'
  ) {
    return;
  }

  var get = descriptor.get,
      set = descriptor.set;

  // 对 value 属性的获取与设置进行拦截
  Object.defineProperty(node, valueField, {
    configurable: true,
    get: function () {
      return get.call(this);
    },
    set: function (value) {
      {
        checkFormFieldValueStringCoercion(value);
      }

      /**
       * 注意此处,每当设置元素的 value 属性时,currentValue 也被修改,
       * 导致 updateValueIfChanged 函数始终返回 false
       */
      currentValue = '' + value;
      set.call(this, value);
    }
  });

  Object.defineProperty(node, valueField, {
    enumerable: descriptor.enumerable
  });
  var tracker = {
    getValue: function () {
      return currentValue;
    },
    setValue: function (value) {
      {
        checkFormFieldValueStringCoercion(value);
      }

      currentValue = '' + value;
    },
    stopTracking: function () {
      detachTracker(node);
      delete node[valueField];
    }
  };
  return tracker;
}

我们来看看真实输入的流程:

  1. 触发输入
  2. 进入 updateValueIfChanged
  3. 获取旧值和新值
  4. 两者不同,设置 value,updateValueIfChanged 返回 true(重要)

再来看看模拟的输入流程:

  1. input.value = ‘changed’
  2. 触发 value 属性的拦截,将内部的 tracker currentValue 更新为 changed
  3. 触发输入事件
  4. 进入 updateValueIfChanged
  5. 获取旧值和新值
  6. 两者相同,updateValueIfChanged 返回 false(重要)

这里模拟输入时,新旧值是相同的,所以 updateValueIfChanged 函数会返回 false,当返回 false 时不会触发 React 的更新机制去更新状态,即无法进入下图的流程:

Image

我们再来看看之前的解决方案:

// 暂存旧值
let lastValue = input.value;

// 设置新值
input.value = 'new value';

// ...

// 获取 tracker
let tracker = input._valueTracker;

if (tracker) {
  // 将 tracker 设置为 旧值,此时新旧值不同,updateValueIfChanged 会返回 true
  tracker.setValue(lastValue);
}

// ...

—end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2165025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

s3c2440——ADC模数转换器,Linux驱动编程——u-boot

一、ADC 模拟:连续;数字:离散。 模拟信号一般指连续变化的电压值。转换的步骤:采样、量化。 ADC中soc电压转为数字信号的方法:逐次逼近法。 分辨率:nbit;表示一个电压比较器比较10次&#xf…

CoreDNS实现跨集群service解析实践

CoreDNS实现跨集群service解析实践 背景介绍使用条件实现方案 CoreDNS是一款使用Go语言实现的专为云原生应用而生的DNS服务器。本文介绍CoreDNS在特定实际场景下的一种进阶使用实践,也许能为其他也在使用CoreDNS做服务发现的同学提供一些启发和思考。 背景介绍 在…

三星推出990 EVO Plus固态硬盘,支持PCIe 4.0性能出色

容量高达4TB,提供增强的性能和能效。性能卓越,随机读写速度分别为为 1,050K IOPS 和 1,400K IOPS。 韩国——2024年9月25日—三星电子于今天宣布推出990 EVO Plus固态硬盘,为其固态硬盘产品线再添新成员。990 EVO Plus 支持 PCIe 4.0和最新的…

Icarus翼星求生教你使用服务器开服

1、购买后登录服务器(百度莱卡云游戏面板) 登录面板的信息在绿色的登陆面板按键下方,不是你的莱卡云账号 进入控制面板后会出现正在安装的界面,大约10分钟左右就能安装完成 2、创建端口 点击目录上的网络,再次页面下点…

kali-linux-2023.4 安装与配置

kali官网 作者:程序那点事儿 日期:2024/01/15 21:34 进入kali官网,点到下载页面 选择安装方式(本次私用虚拟机安装)。裸机安装是指,先要安装虚拟机(例如:CentOS7&#xff09…

【算法】贪心+堆排序实现大根堆及标准库容器类的融合使用

📢博客主页:https://blog.csdn.net/2301_779549673 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 CSDN🙉 📢未来很长&#…

centos7安装Redis单机版

一、检查是否有GCC环境 gcc --version # 提示-bash: gcc: 未找到命令 说明没有gcc环境# 安装gcc环境 yum install gcc# 如果yum源报错 # 1.检查网络是否正常 ping www.baidu.com # 2.备份当前的yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo…

IntraWeb制作汉堡菜单

IntraWeb开发web网站时如何制作手机页面? delphi源代码:示例两列布局带顶部汉堡菜单(兼容电脑与手机) 功能:交互式网页,两列布局,顶部汉堡菜单,点击汉堡图标关闭左侧栏,…

【NLP】基于“检测器-纠错器”中文文本纠错框架

前言 许多方法将中文拼写纠正(检测和纠正给定中文句子中的错误字符)视为序列标注任务,并在句子对上进行微调。一些方法使用错误检测器作为初步任务,然后将检测结果用于辅助后续的错误纠正过程。然而,现有方法在使用检…

RIFormer:保持你的视觉主干有效但移除令牌混合器

摘要 https://arxiv.org/pdf/2304.05659 本文研究了如何在去除其基本构建块中的标记混合器(token mixers)的同时保持视觉主干的有效性。标记混合器作为视觉变换器(Vision Transformers, ViTs)的自注意力机制,旨在实现…

java项目之新闻稿件管理系统(源码+文档)

项目简介 新闻稿件管理系统实现了以下功能: 新闻稿件管理系统的主要使用者管理员功能有个人中心,用户管理,记者管理,审批员管理,新闻分类管理,新闻信息管理,系统管理等。记者发布新闻信息&…

uniapp 整合 OpenLayer3

安装openLayer插件 命令行&#xff1a;npm install ol 安装sass插件 命令行&#xff1a;npm install -D sass 使用方法&#xff1a; *** *** <style scoped lang"scss"> </style> 安装ElementPlus 命令行&#xff1a;npm install element-plus -…

汽车零部件开发流程关键阶段

目录 1、定点阶段 1.1、定点前的准备工作 1.2、定点决策过程 1.3、定点后的工作交接 2、A样阶段&#xff1a;设计验证与基本功能实现 2.1、样件制作&#xff1a;从设计图纸到实物转化 2.2、功能测试&#xff1a;初步验证与性能评估 2.3、评估与优化&#xff1a;A样阶段…

Java数据库连接jdbc

Java数据库连接jdbc 导入java包 1、根目录&#xff0c;新建一个lib目录&#xff08;Dire&#xff09; 2、将jar包放入lib目录下 3、File -> Project Structure&#xff08;项目结构&#xff09; 4、Libraries-> ->java->找到项目的lib目录 5、Apply->OK使用JD…

下载2001年版英特尔开发手册与使用网易有道词典

本专栏的任务&#xff0c;是翻译2001年版英特尔开发手册的第3卷。上一节&#xff0c;我写了开篇语。本节&#xff0c;我是打算将这个版本的英特尔开发手册的下载方式公布出来。使得大家可以将其下载回去。如果你看的块的话&#xff0c;你可以自行翻译与学习。 一. 下载英特…

数据结构const char *INSTNAME[]

代码片段解析 #include <cstring> #include <fstream> #include <sstream> #include <string>const char *INSTNAME[]{"lui", "auipc", "jal", "jalr", "beq", "bne", "blt…

从理论到实践:解锁《数字化专业知识体系》助力企业数字化转型的落地之道

全面解码数字化转型——从理论构想到实践落地 在全球数字化浪潮的推动下&#xff0c;企业正面临前所未有的变革压力。虽然数字化转型的概念已经深入人心&#xff0c;但将其从战略蓝图转化为实际成果的过程仍充满挑战。《数字化专业知识体系》&#xff08;《Towards a Digital …

双目视觉路线,为什么一直没有存在感

“在大疆之前没有人做双目&#xff0c;现在基本上主流的都是单目加多传感器融合&#xff0c;推给车企的时候就会经历一个更长的过程。”一位前大疆车载员工曾这样向雷峰网《新智驾》表示。 双目视觉方案在车载上的应用起起伏伏&#xff0c;从早期的高端车型专属&#xff0c;到…

快手B端商业化技术探索:基于LLM构建智能RAG与Agent平台

导读&#xff1a;大模型技术正以前所未有的速度与各领域融合&#xff0c;为各行各业带来变革&#xff0c;围绕快手B端商业化的业务场景&#xff0c;本文详细阐述了构建基于LLM的Agent技术平台的策略、挑战及解决方案&#xff0c;为您带来宝贵的见解与启示。 一、大模型应用建设…

一日连发两款视频大模型,火山引擎杀疯了!

9月24日&#xff0c;字节跳动旗下火山引擎在深圳举办AI创新巡展&#xff0c;并首次对外发布豆包视频生成-PixelDance、豆包视频生成-Seaweed两款AI大模型&#xff0c;并公布了多项AI大模型的全新升级&#xff0c;以一种全新的姿态迎接AI时代的到来。 雷科技此次受邀参与巡展&a…