失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > input重置为空后点击出现上次的值_上次玄乎的问题后续来了

input重置为空后点击出现上次的值_上次玄乎的问题后续来了

时间:2023-03-15 11:12:10

相关推荐

input重置为空后点击出现上次的值_上次玄乎的问题后续来了

上次有一个玄乎的问题,那时候玄学解决。但是过了几天,再次出现,这一次,已经查出原因,彻底把它解决了。点击这里回顾

问 题 分 析先捋一下整个过程:

1. react源码错误,必然是有react之外的原生dom操作

2. 确认过代码,没有任何其他原生dom操作

3. 对方在控制台做了dom操作?不可能,无技术背景

4. 那只能是浏览器插件、中间人注入(基本不可能优先级调最低)、翻译

5. 忘了上次打断点的事情吧,不能投机取巧

上次的经验告诉我,直接远程控制是最好的方法。于是马上连上了远程控制。检查了一下浏览器插件,没有什么插件有影响——浏览器插件pass。确认一下是否翻译,问了对方说有没有开了翻译,对方说没有(远程桌面看不见弹出菜单的,所以需要人家告诉我)

我:"have you opened the extensions?"

对方:"yeah, these"

我:"did you turnedthe translation on?"

对方: "i didnt translate on that website"

ok,人家说没有翻译,那我就假设这是实话。既然问题发生的根本原因就是有react之外的原生dom操作,那就是dom节点数很有可能不一样。于是我在控制台输入了一下$$('*'),发现对方电脑上是2400个节点。在我电脑上输一下,只有2000个节点。让同事帮忙看看,一样也是2000个节点。于是我决定对比一下第一个不一样的节点是怎样的,在对方的电脑控制台上输了一段简单的脚本:

$$('*').reduce((acc,{tagName})=>`${acc}${tagName},`,'')

我:"could you please copy the txt and send me"

于是我拿到了用户整个页面所有的标签字符串集合,在我打开的页面的控制台下,和我的对比一下:

var arr = otherHtml.split(',')$$('*').findIndex(({tagName},i)=>tagName!==arr[i])

发现index为103,找到第103个节点,发现是一个link标签,引入了下的一个css,而且html这个标签多了一个叫做translated-ltr的class。顾名思义,翻译实锤了

于是,再继续展开主内容,发现对方的页面上多了很多font标签!!

果然,还是开了翻译,只是人家“觉得没有开”。其实,很有可能是之前设置了一律翻译,所以后面就一直不用管,所有的网站都会自动翻译。接着让用户按照我的要求,将翻译关掉。最后,多次重复的操作,问题也没有出现了

我:"look at this wiki, and close the translate completely"

对方:"as you wish"

(一会后)

对方:"okie done"

其实,估计之前大家都是脚手架一把刷,并没有注意到html的lang的值,而且我们这个系统都是英文的。

<html lang="zh-cn">

于是出现了一个所有的内容都是英文的“中文”页面,到了海外Chrome翻译的逻辑就是,这是“中文”页面,需要自动翻译,然后就“英文翻译成英文”,视觉上无变化,实际上dom节点已经多了很多font

复 盘上次

于是我还是想看看为什么上次打断点就没事了,打开维基百科(/wiki/Wiki )试一下,在开启了翻译的条件下打断点会发生什么。打开source面板,勾选了load事件

自动翻译也开启

刷新页面,发现一进来的时候,一切安好,html标签是这样

class="client-js" lang="en" dir="ltr">

点了两下下一步的时候,html标签发生了变化,核心特征:有translated-ltr类

class="client-js translated-ltr ve-not-available" lang="zh-CN" dir="ltr">

再看看element面板,很多font包裹

实际上这就是一个页面load成功后,Chrome的翻译功能去拉css和js回来、修改页面内容的过程。复盘一下上次能解决问题的断点操作:

我在报错的发生前最后一个接口的返回打了断点,勾选了error事件的断点

页面进来,有一个cors报错,error卡住。此时已经有请求出去了,断点卡一下争取到了时间(你看起来是pending,实际上response已经到你家门口了)

再点下一步,前面的数据秒出,一瞬间又卡了,因为最后一个接口也回来了

此时还没到拉翻译资源的时候,但页面已经展示完整。我点一下按钮,成功越过翻译导致的页面元素错乱。这是一个创建按钮,创建成功了后面就是用户自己操作了

因为创建是频率稍微低一些的行为,所以几天内再无收到反馈

出现问题通常是setstate后删掉某个元素,那个元素追溯不到报错了。这里点了按钮的确是会删掉按钮并切换页面内容

尝 试 错 误

继续来作死,一起看看怎么样才能把react玩坏

const { useState, useLayoutEffect } = React;export default function App() {useLayoutEffect(() => {const font = document.createElement("font"); const app = document.querySelector(".App"); // 制造font包裹的效果,模拟翻译的效果,破坏原有结构 while (app.firstChild) {font.appendChild(app.firstChild); } app.appendChild(font); setTimeout(() => {// set个state看看setShow(false); }, 1000); }, []); const [show, setShow] = useState(true); return ( <div className="App"><h1>Hello CodeSandboxh1>{show && ( <>123123<h2>Start editing to see some magic happen!h2> >)} div> );}

预期效果出现了:

其实也不需要手动改,你只需要右键开启翻译为中文就可以复现了。问题根源在于react提前把parentNode存起来了,所以操作的时候找不到子节点

解 决 方 法

错误边界组件

利用react的两个生命周期来感知翻译错误,然后展示兜底ui,提示用户关掉翻译。并给出操作文档链接(/chrome/answer/173424?co=GENIE.Platform%3DDesktop&hl=en )。使用的时候只需要用TranslateErrorBoundary包一下组件即可

class TranslateErrorBoundary extends ponent {constructor(props) {super(props); this.state = { translateError: false }; } static getDerivedStateFromError() {if (document.documentElement.classList.contains("translated-ltr")) {return { translateError: true }; } } componentDidCatch(e, info) {// 上报翻译错误 report(e, info); } render() {if (this.state.translateError) {return ( <><strong> translate error! you' d better to turn your google-translate off and reload. seestrong><a target="_blank" rel="noopener noreferrer" href="文档链接"> 文档a> >); } return this.props.children; }}// usage<TranslateErrorBoundary> <Cpn />TranslateErrorBoundary>

不要让一块可删改的react元素最外层存在文本节点

话不多说,看?

<div className="App"> <h1>Hello CodeSandboxh1> {show && ( <>123123<h2>Start editing to see some magic happen!h2> > )}div>

这一块,有最外层的123123文本节点,所以翻译了会报错:

{show && ( <> 123123 <h2>Start editing to see some magic happen!h2> >)}

为什么呢?先看看翻译后结果,发现原本想删的节点是"123123",而他父节点却再也找不到它了

{show && ( <> <font><font>123123font>font> <h2><font><font>Start editing to see some magic happen!font>font>h2> >)}

改正措施: 加上span标签,不要让123123裸露

{show && ( <> <span>123123span> <h2>Start editing to see some magic happen!h2> > )}// 翻译后{show && ( <> <span><font><font>123123font>font>span> <h2><font><font>Start editing to see some magic happen!font>font>h2> > )}

因为最外层的是span,所以即使加了font,也是在span内部加了,删除元素的时候找的是span,都不会出问题

再看一个?

{label !== undefined ? (<div>{label}div>) : null}{children}</div>

这里的话,label就是纯文本。经过上面的例子,相信大家都知道{label}那里要套一个span了。但是这还是有风险:如果这个组件对外部使用,外部靠children传进来,意味着children的内容是多变的,比如传一个字符串进来,setstate后是一个其他节点,那么问题再次出现

错误条件再次重复一遍:一块可删改的react元素最外层存在文本节点。此时children是一块元素,而且是可变的,最外层就是children这个对象的最外层所有节点,其中存在一个文本节点是字符串,因此满足出错条件

例如children是文本节点textNode1,那么正常情况下setstate后如果children发生变化,删掉textNode1的方式就是textNode1ParentNode.removeChild(textNode1)。如果翻译了,文本节点包了两层font,那么textNode1再也不是textNode1ParentNode的子节点了。此外,即使把外层div换成span、section、article同理,都会出错

推论:不要在任何元素下直接裸露可变文本节点

代码都是自己写的,像props.children这种那么灵活的,尤其是要注意一下,如果是可能有文本节点的最好包一个span,确认没有的就可以不用包,防止外国用户翻译后源码出错。其实可以写一个工具,扫一下ast,发现有裸露文本节点的自动包一层span

要不,提个issue问问react那边可不可以不把parent节点先存起来,删元素的时候直接node.parentNode.removeChild?

总结

1. 使用数据驱动视图的框架如react、vue,如果遇到源码错误,考虑一下是不是有原生dom操作打乱了

2. 如果确认不是原生dom操作导致,考虑一下浏览器插件、翻译

3. 确实需要在react、vue中使用原生操作,需要考虑到这个隐患

4. 国际化的业务,如果出现这种问题,建议首先从浏览器翻译开始排查

5. 不要让一块可删改的react元素最外层存在文本节点,确认会有可变文本节点,需要套一层span

b站全灰了,但我一下把它弄回来了——css 滤镜

1年多职业生涯中最玄乎的线上问题

前端与前端联调的姿势

[js算法]手把手带你从leetcode原题——【两数相加】到大数相加

内功修炼之lodash——Object系列

那个前端写的页面好酷——大量的粒子(元素)的动效实现)

内功修炼之lodash—— clone&cloneDeep(一定有你遗漏的js基础知识

如果觉得《input重置为空后点击出现上次的值_上次玄乎的问题后续来了》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。