ustbhuangyi/vue-analysis

Vue原理问题讨论, 关于vue与原生js混用

afenotes opened this issue · 2 comments

代码如下:
https://jsfiddle.net/afenotes/bkjxvr07/

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>VUE</title>
	<script src="https://unpkg.com/jquery@1.12.4"></script>
	<script src="https://unpkg.com/vue"></script>
</head>
<body>
	<ul id="list">
		<li v-for="(l,index) in list" v-bind:key="index" :ref='"li"+index'>{{l}}</li>
	</ul>
	<script>
		var ul = new Vue({
			el: '#list',
			data: {
				list: ['apple','banana','orange']
			}
		})
		setTimeout(function(){
			// $('<li>grape</li>').prependTo($('#list'))
			$('#list').children().first().text('grape')
		}, 3000);
		setTimeout(function(){
			ul.list = ['Bob','Tom','Jim']
		}, 6000);
	</script>
</body>
</html>

初始渲染结果:

  • apple
  • banana
  • orange

3s后,渲染结果:

  • grape
  • banana
  • orange

6s后,实际渲染结果:

  • grape
  • Tom
  • Jim

期望结果:

  • Bob
  • Tom
  • Jim

为什么第一个元素没有更新?猜测virtual dom到真实dom之间的映射丢了,求指点。
(知道这个混用不对,好奇背后的原理)

@ustbhuangyi 大神回复如下:
你这个问题的原因定位了,首先 li 包裹的文本元素会在 createElm 阶段创建 vnode.elm = nodeOps.createTextNode(vnode.text),然后你用 jQuery 修改了这个文本节点,文本就已经变了。由于你的 v-for 的 key 是 index,那么在 patch 过程中,新旧 li 始终被认为是 sameVnode,也就会执行 patchVnode 方法更新,对于文本节点,最后会更新
else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text);
}
但是这个时候这个 elm 是之前 oldVnode.elm,它的文本是 'apple',但是现在界面上显示的是用 jQ 修改的文本节点 grape,也就是说你只是把 apple 修改成了 Bob,但实际上界面上的 grape 你并未修改,这就是 DOM 操作和 Vue 混用的坑。
这里你可以把 v-for 的 key 改成 l,这样的话再更新的过程中新旧 li 就不是 sameVnode 了,它就会创建新节点,删除旧节点,就可以正常更新了

通常是不建议 v-for 的 key 用 index,坑比较多,删除节点的时候会遇到

感谢大神!!!

<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>VUE</title>
    <script src="https://unpkg.com/jquery@1.12.4"></script>
    <script src="https://unpkg.com/vue"></script>
</head>

<body>
    <ul id="list">
        <li v-for="(l,index) in list" v-bind:key="index" :ref='"li"+index'>{{l}}</li>
    </ul>
    <script>
        var ul = new Vue({
            el: '#list',
            data: {
                list: ['apple', 'banana', 'orange']
            }
        })
        setTimeout(function () {
            // $('<li>grape</li>').prependTo($('#list'))
            $('#list').children().first()[0].childNodes[0].textContent = 'grape';
        }, 3000);
        setTimeout(function () {
            ul.list = ['Bob', 'Tom', 'Jim']
        }, 6000);
    </script>
</body>

</html>

这个问题很有趣,原理就像大神所说的之前的文本节点对象已经被jquery给更改了,所以为了不让其更改文本节点对象,可以直接更改文本节点的textContent,这样就可以避免第一个元素不更新的问题了