记录一次艰难,却很有意思的问题解决经历-React input

8,858 阅读6分钟

背景

公司有一个业务,希望唤起第三方客服时,自动发送一条消息,类似淘宝里退换货,唤起客服就有有关退换流程的信息。

解决之路

1. 询问第三方

毫无疑问,第一步当然是问第三方客服是不是支持这样的功能,很遗憾,不支持。技术文档里有一个初步唤起的功能,可以自动发送一个打招呼的信息,但这只限于第一次,之后就无法自动发送了。

此路不通,另寻他法。

2. 模拟用户输入

既然没有现成的方法,只能曲线救国。

自动发送,当然是让程序模拟用户发送一条信息。我想这个应该很简单,调起客户端窗口后,定位到输入框,再模拟回车就行了。情况就是下面这个情况:

但万万没想到,不知不觉走进了一个巨大无比的坑。

a. 模拟输入尝试

客服聊天框放在一个iframe里,输入框有id,定位不是问题。开开心心的给他设置一个value, 再模拟一个回车,运行!然后,翻车了,输入框有输入的消息,但没发送出去。

// 设置发送信息
var inpEle = window.frames['chat'].document.getElementById('_MEIQIA_INPUT')
inpEle.value = '测试信息'
// 模拟回车
var event = document.createEvent('Event')
event.initEvent('keydown', true, false)
event = Object.assign(event, {
  ctrlKey: false,
  metaKey: false,
  altKey: false,
  which: 13,
  keyCode: 13,
  key: 'Enter',
  code: 'Enter'
})
inpEle.focus()
inpEle.dispatchEvent(event)

b.是因为回车事件不成功吗?

监听keydown事件,打印出内容,看看回车到底生没生效。

inp.onkeydown = function(event) {
  console.log(event.keyCode)
}

结果发现是生效的,打印的keyCode13

c. 输入和回车分开来测试看看

  • 把回车的模拟去掉,当输入框聚焦时,手动按键盘试试。结果也不行,手动按的回车keyCode在控制台打印出来了,但信息就是没发出去……

  • 把模拟回车设一个延时,然后先在输入框输入文本,等延时到了让模拟回车来提交发送。发现结果是可行的。

    ……(以上过程包含尝试各种模拟输入和模拟回车的方法,以及一根忧郁的烟)……

到这里,分析原因,说明模拟输入的值无效,回车发送的是一个空值,而空值是不会有任何动作的,所以看起来发送不成功。但为什么模拟输入无效,百思不得其解。

d. 偶然发现初始化时的一些蛛丝马迹

有几次刷新页面,突然自动发送成功了!只要刷新页面后第一次运行,就能成功,之后就又不行了。为什么刷新后就能自动发送成功呢?这时已时晚上,回家去想了一宿。忽然开窍,莫非这个聊天控件在本地有存值,这样只要发现有值,就会发送?而这个值是输入时动态生成的,我用模拟输入没法触发input事件,因此没保存这个值?我很想测试,期待第二天上班。

急急赶到公司,迫不及待的看了下localStorage, 果真!里面存了输入框已经输入的值!真是喜出望外,那么我是不是只要修改这个值再模拟回车提交就行了?胜利似乎已经在前方了!

经过几番尝试(这个几番几乎折腾了我一个上午),发现这个localStorage缓存值的作用只是为了下次初始化时,自动将信息填写在输入框,即记住了用户上次未发送的消息。因此,它,并没什么卵用。

这个时候我告诉产品小姐姐,目前遇到困难,可能只能在第一次时能自动发送,之后就不行了。产品小姐姐体谅的说,看看是否结束对话时就可以自动发送,如果是这样那就没关系。

遗憾的是,结束对话,只是不能再发消息,重新开始对话并没有重新初始化,也就不会自动发送了。于是我重新投入研究,我不服,一定能找到办法实现自动发送!

e. 冷静分析为什么模拟输入无效

回到之前的路,已经知道根本原因是因为模拟输入无效,但为什么无效呢,我把textarea的value和innerText都设置了,但在控件里始终是空值。

inpEle.innerText = '测试消息'
inpEle.value = '测试消息'

更奇怪的是,模拟输入完,且聚焦在输入框时,只要我点击其他地方,输入框的内容就被置空了! 我想,在输入时,应该有把值存在一个变量中,由于模拟输入在sandbox browsers中是无法触发input事件的,自然变量就得不到值。这就陷入死结了。我不死心,我想尽力看看它们绑定的input事件的函数是什么,于是我去浏览器的EventListener中到函数的定义。

定义是在一个压缩的js中,我有心里准备,我试图把它反解压出来。

f. 曙光来临

正当我准备去反解压时,我看到了一个耀眼的关键字,React!这说明什么?这说明这个压缩的js文件不是他们自定义的,是React的文件,而且是react-dom.product.min.js,从这个文件找源文件,那不是跟吃饭那么简单么?虽然我没学React,但我懂Vue啊,大家有些原理是相通的,何况从文件名看,这个是有关react dom操作的,那大家有很多相似了。

不过我还是没急着去找源文件,而是去搜索了“React input 模拟”,大概是被这个问题折腾太久了,老天都感动了吧,我自己都没想到这样直接去找问题,我会直接去反解压js文件。

然后,就在搜索列表的第一页,看到了一篇文章:

它给了我要的答案,感恩这位“拎着激光炮的野人”。它描述的情况和我遇到的情况几乎一摸一样,也是模拟输入的内容会重新被置空。到这里才明白原来是因为React 的原因,虽然我没研究react,但从这里可以大致了解,是类似双向绑定的机制阻碍了模拟输入吧。

至此,问题终于解决了。

我兴奋地告诉产品小姐姐,问题突破了!自动发送,不限时间,不限内容。

结语

这次问题的解决是众多解决问题经历中的一个,但很有意思,也很有成就感,其中包含着挫折、抑郁、激动、期待、好奇、不服、奋发,最后兴奋的复杂心情,记录下来,以后没事回顾回顾。

还有就是,意外看到了js的dom操作的很多知识,发现这块非常缺乏,知道得越多,越觉得自己无知。

哦,还有,百度找的那些js的答案,80%是jQuery的,没几个原生js的,几乎要气绝,好在还能上上google。感恩!