你是否遇到过这样的场景:在浏览器中使用谷歌翻译功能后,网页突然崩了,控制台错误:
这一切的罪魁祸首,其实是谷歌翻译悄悄修改了你的页面结构!下面就来简单解释原因和解决方案。
谷歌翻译如何“搞破坏”?
我们先看看谷歌翻译是如何工作的。
下面是未翻译的文字的 HTML 结构:
翻译之后文字的 HTML 结构是这样的:
翻译引擎将纯文本节点包裹在<font>标签中,这种看似无害的操作,却为现代前端框架埋下了定时炸弹。
两大致命场景
场景1:状态更新失效
对于像React这样的现代前端框架,它们依赖虚拟DOM来提高性能。当组件的状态或属性发生变化时,React会基于虚拟DOM的状态来操作真实DOM。然而,当真实DOM被谷歌翻译修改后,原节点的位置和状态与React的预期不一致,导致更新操作无法执行,页面因此无法正确更新。
例如,在以下计数器组件中:
import React, { useState } from"react";
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<h1>example</h1>
<p>count:{count}</p>
<p>{count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
);
};
exportdefault Counter;
在这段代码中,两种显示方式在操作时的结果完全不同:
可以看到,使用<p>count:{count}</p>显示时,当页面使用翻译功能后,点击按钮将不会再更新,而<p>{count}</p>在使用谷歌翻译前后都是正常更新。
- 对于第一种情况:谷歌翻译将原始文本拆分为多个嵌套的 <font> 节点,破坏了 React 原本控制的文本结构。当 count 更新时,React 尝试直接修改原始文本节点的内容,但此时该节点已被替换为 <font> 节点,导致更新失效。
- 对于第二种情况:即使谷歌翻译包裹了 <font> 标签,但 整个 <p> 的唯一子节点仍然是动态值 {count} 对应的 <font>。React 在更新时可以通过对比虚拟 DOM 发现 <font> 内部的文本变化,从而正确更新内容。
场景2:应用崩溃
谷歌翻译的干扰还可能导致应用崩溃。当应用试图从DOM中移除一个已被谷歌翻译修改的文本节点时,会抛出一个NotFoundError错误。这是因为当应用试图从 DOM 中移除一个条件渲染的文本节点时,这个节点已被谷歌翻译卸载。
下面来看一个例子:
import React, { useState } from "react";
const ToggleText = () => {
const [showText, setShowText] = useState(true);
return (
<div>
<button onClick={() => setShowText(!showText)}>切换文本</button>
{showText && "Hello, World!"}
</div>
);
};
export default ToggleText;
翻译后的文本被<font>标签包裹后,不再是父元素的直接子节点。当状态切换触发DOM卸载时,React会因找不到原始节点而抛出NotFoundError,最终导致组件树崩溃。
如何解决?
尽管这个问题在 React 社区中早已被提出,但官方至今都没有提供完美的解决方案。Dan 给出了一个方法来修复,但是会牺牲一定的性能,需要开发者根据实际情况来权衡是否有必要添加这段代码,并且这段代码也并没有解决本质问题。
图片
其实,对于上面提到的例子,只需要添加标签来分离文本和动态内容,这样 React 就不会直接删除和插入文本节点,就可以避免这个问题。但这只是其中一种情况,我们很难避开所有导致应用崩溃的情况。
有以下解决方案可供参考:
- 阻止部分翻译 :可以通过给需要保持原样的部分添加 notranslate 属性来防止谷歌翻译对其进行操作。
<p translate="no"></p>
- 完全禁用翻译 :如果希望整个页面都不被谷歌翻译,可以在 HTML 头部加入以下meta标签:
<meta name="google" cnotallow="notranslate">
- 使用 Error Boundary 隔离错误: 对每个组件使用 Error Boundary,防止错误扩散到整个应用,导致应用崩溃。
<ErrorBoundary>
<ToggleText />
</ErrorBoundary>
- 必要时进行国际化:如果应用需要支持多种语言,可以考虑实现应用的多语言支持,而不是依赖谷歌翻译。这样可以更好地控制文本的显示和更新,告别第三方翻译插件的 DOM 污染。