由Chrome翻译引出的Vue更新视图失败的问题

起因

在某个项目中,部分电脑上会出现保费以及价值等文本元素在视图上没有发生改变,但是数据是已经更新了的。

在排查过程中,发现其他的元素的显隐规则是能够正常运行的,并且在更改了保费元素的类型为input元素后,也是能够正常更新视图的。

在查看页面元素时,发现chrome开启了翻译功能,导致文本元素的节点发生了变化,如下面所示:

1
2
3
4
5
6
7
8
9
10
<!-- 原元素 -->
<span>12345元</span>
<!-- 翻译过后的元素 -->
<span>
<font style="vertical-align: inherit">
<font style="vertical-align: inherit">
12345元
</font>
</font>
</span>

这个项目在html文件上没有指定语言为中文,翻译后才会有节点的变化。
根据这个元素的变化,初步确定是Vue更新视图时因为元素发生变化,导致无法正常更新视图的问题。

Vue patch算法更新视图失败的原因

Vue的patch算法,是先对vdom进行比对,判断是相同、新增还是删除节点而做出不同的操作。因为是对vdom进行比对,所以chrome翻译导致的新增节点,无法重新映射回vdom节点。

所以在比对时,vue会认为本次更新时,这个文本元素只有值的变化,所以最终只更改了span标签下文本节点的textContent,但这时span标签下并没有这个文本节点,所以即使更改了textContent,也是无用的,因为它并不能映射到真实元素上面。

解决办法

  1. html需要指定正确的语言,这样在同个语言环境下,即使强行翻译页面,chrome也不会在文本元素下增加新的元素。(但是在不同语言环境下,如果将页面翻译成其他的语言,也依旧会存在视图更新失败的问题)
  2. 将模板语法两个大花括号改为v-text,虽然大部分资料上写的是两个大花括和v-text没有什么区别,但是vue的模板编译和patch算法对这两种语法的处理形式是不同的。
    • 模板语法在编译过后,就是一个正常的节点关系,在patch的时候也依赖于vdom和真实dom的对应层级关系。
    • v-text编译后本质上是在该节点上将textContent属性和值绑定在一起了,并且在patch时,v-text会将该节点下的子节点都清空,然后再更新textContent属性。这也是为什么Chrome翻译页面后,在文本节点下新增子节点,Vue也能够正常更新视图的原因。

模板语法与v-text编译后的区别

1
2
3
4
<div id="app">
<span>{{ msg }}</span>
<span v-text="msg"></span>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('span', [_v(_s(msg))]), _c('span', {
domProps: {
"textContent": _s(msg)
}
})])
}
}