小伙伴投稿:vue2加了属性丢了样式
今天这个小伙伴投稿的bug
特别神奇哈
大家看这个是一个vue2的demo
然后这页面里用了一个组件
然后组件有一个loading的插槽
里边就写了一个loading
然后下面正文
然后这个组件里边也特别简单
它默认呢显示的是loading的内容
然后呢在timout, 加载完之后一秒
让loading等于false
它就显示正文的内容
就这么简单
但是呢我们先来看一下效果啊
刷新loading一秒,正文
这没有问题哈
但是大家看啊
就就是这个loading里这个div
我把这个a属性去掉
大家看这个正文其实是应该带样式的
也就说
只要加了这个a属性
他这个样式就没有了
而且呢
如果我们让他默认就加载正文
就是从来不加载这个loading 我们看啊
他是没有问题的
只是说让他只要是涉及到一秒的切换
他就有问题
那刚开始看到这
我觉得这个问题应该很简单啊
肯定是这个样式没有命中嘛
我们看一下他下边用了scoped
然后empty
然后我们看一下啊
大家看这个empty现在是这样
但是它缺了一个这个scope ID嘛
我们把它手动先加上看一下
大家看这个样式就有了
也就是说他通过这一秒的切换
这个scopeid丢了
那看到这呢
我就觉得这个问题应该没有那么简单
因为我们都知道哈
vue的底层它用了虚拟节点
就说它会复用虚拟节点
当他切换的时候
很有可能
这个div是复用了前面的
被隐藏起来的那个div的虚拟节点
所以为了验证这个结论哈
我们可以先在页面上测一下
加一个key
我们在这个div上加一个key
随便写吧
大家看这就没有问题了
大家看,这个empty
现在是有这个scopeid了
到这呢
我们就基本确认了之前的猜想
以及这个问题是可以被解决的
但是这个小伙伴的问题是为啥
所以呢我们必须从源码入手
所以呢我们先把vue的源码下载下来
然后呢我们看一下本地
这个项目用的是哪个vue的版本
比如这个2.6.
那么我们在vue项目里边
就基于2.6.14这个tag
创建一个2.6.14的分支
在这个分支里去找线索
然后呢注意我们怎么去找线索呢
这个项目里这么多文件
我们怎么知道相关的代码在哪呢
给大家推荐这个Codegeex哈
然后这个之前我视频讲过
大家需要的话可以往前翻视频
然后我们用它这个workspace的功能
大家看啊
在这里大家看,@workspace
然后问他
这个项目
哪个方法负责在vue(口误:v-if)变化的时候
更新页面的DOM
然后他告诉我这个方法
但注意啊
他下面说了一句
实际v-if更新
是由vue的虚拟DOM diff算法处理的
所以呢
我又问他虚拟DOM的diff算法在哪里
然后他给我找了一个地方在这里
然后呢我就把这个文件打开
大家看啊
好,有这个文件
然后呢
我还不知道我说具体是哪个函数
他告诉我是patch函数
所以呢我们就找patch函数
大家看在这里
我把这个旁边的这个先关掉
他说在这个函数里边
好那有了这个函数
我就可以简单先读一读了
读的时候我们会发现
这里边其实真正的含义在这里
就patchVnode的这个方法
所以呢我们再去这个方法看
在这里
那接下来呢
我想调试怎么办呢
我们找一个像这种注释哈
就是基本上
我们猜测它能在全项目里边
有这么一行
然后我们去到网页里哈
在network上用快捷键Command + Option + f
Windows应该是control + shift + f
然后在这找
刚才这段
大家看我们找到两条
像这个第一个这种我们就不用管了
看不懂我们打开
第二个
这样的话我们基本就找到这个代码了
我们在这打一个断点
看能不能断住
一秒之后
大家看走过来了
我们看这个vnode
它是一个div
然后呢大家看它有children
children里还有一个div
大家看这个data
这个是我们刚才设的key
然后它有empty这个class
就是说我们现在这个vnode是
这个div的父极元素
我们先把这个key去掉吧
为了调试哈
我再重新刷新一下啊
因为我刚才把key去掉了
这个vnote不是我们要找的哈
我们再过一下
它又走进来一遍
我们再看这回
它有个data
然后 staticClass等于empty
这就是我们设的这个
empty的class比如说现在断住的这个地方
就是我们要找的地方了
我们现在把它加一个条件
我们让它vnode……
vnode.data.staticClass === 'empty'
就是这个条件才断住
这个就是我们要找的节点
好接下来
我当时呢
就是把它差不多的就是过了一遍
大概看了一遍哈
没发现什么
我先就直接过了
然后呢我心想
那我就再把它这个有问题的再放回来
就在这个组件里边
我把这个再去掉
我看是不是还能断住
大家看!他不走断点了
就是刚才加了这个属性的时候
他就走这个断点
去了这个属性他就不走这个断点了
就很神奇吧
所以呢我心想
那我再把它加回来
刷新大家看
又走断点了
然后呢我在下边找堆栈的信息
现在是第一个
我们去到第二个
就是说调用它的地方
就是从这进去的
这个patchVnote
我们不管
我们在他这个elseif这儿
在这打
它肯定是没有满足这个条件嘛
所以它没有进去对吧
然后呢我们再把这个放开
再回到刚才这个模式
再刷新
OK现在走到这了对吧
我们单步跳过
大家看啊
他就没有进去
没有进去
没问题我就继续往下走
看他到底走哪了
大家看,下边这一块有一个createElm
也就是说:他这个儿没有共用别人的虚拟节点
他自己创建了一个element
我们进去看看
然后呢
我看着看着这块有个意外收获
大家看我看到了一个这个
setScope(vnode)
我们走到这
然后再进去
大家看他在这
其实就是在设置这个scope ID
也就是说这个方法是个公用方法
所以呢我们把它先复制过来哈
我们再刷新一下
还是走到这儿
我们看看它为什么不走这个里边儿
也就说这个方法
sameVnode(……)
这个方法返回了一个false
我们先单步跳入进来了
然后这个方法很简单
就这么一行代码
但是呢他现在就返回的是false
我们把它全选看一下
大家看:false
好没问题
我们先执行过
我们再看一下刚才这个
刷新还是这个sameVnode
我们进去
然后还是选中这大块
打开它是处
那我们先把这个方法先给它拿出来
这个里边太小了
这个方法呢
大家看这有一个大括号
它俩是一对的
也就是说从这往后它分两部分
上面是true
同时呢下边这块也是true
他就是一个true
现在这个模式肯定两个都是true
我们再改回去哈
回到这个模式
还是刷新一下
然后呢我们走进去
我们先看这一块
现在这个是有问题的
这个是true
没有问题
那也就是看后边的
我们看后边整个这一块
大家看是false
它就变成false了
那下边这块我们再看哈
我们先把这个大块删掉
下边这块它又有一个或者
这个或者的优先级比较低
它会先算浅两边的
并且最后取或者
那它false
肯定就说明这两块都是false
我们先来看上面这块为什么是false哈
我们一点点看
我们选中几个条件
比如选中到这现在还是true
我们再往后
再看这个就是false了
再看这个
这个是true
也就是说它这里边唯一有一个条件
就是这个条件是false
导致整个这一块儿就变成false了
我们来看一下
isDiff(a.data)…… b.data
我们看b.data是什么
里边儿有一个staticClass
那a.data呢
到这儿是undefined的
我们再把刚才这个恢复成这样
把这个a=1上
我们再刷新
还是,进到刚才这个位置
我们再来看a.data
大家看到了吗
a.data就是 a:1 了
然后b.data呢
还是刚才这样
那isDef根据这个名字(is defined)我们就能看出来
其实它就是判断它有没有a.data
或者有没有b.data所以
只要我们写了一个属性
它就有甚至这里随便写
我们再来试一下
大家看a.data有了
b.data也有了
所以呢它俩是相等的
所以整个呢
它就会返回一个true
大家看返回一个true
如果它返回true呢
我们再回到上一级
它就会走到里边
里边就会共用现有的一个虚拟节点
这个虚拟节点就是这个变量
这个oldStartvNode
大家看它里边有个children
children还是个div
然后div里边还有children
是span就说它这个被共用的虚拟节点
就是它在组件里的这个div
然后
因为这个组件本身它是没有scope的嘛
所以这个虚拟节点
被共用到page里边儿了
导致它就没有scope
所以它就没有命中样式
所以到这为止
我们就知道为什么它加了个属性
那边就没有样式
好
接下来我们可以再来一个好玩的验证
啊我们先把这个放过一下
现在是这样
没有样式的哈
接下来我们想办法
用我们刚才找到那个setScope那个方法
调用一下
在哪调用呢
它就在这个patchVnode里边
我们再刷新一下
过一下我们进到这个方法里
比如说我们在这下边随便找个地方
比如他在这做了很多update的东西
我们就在下边这个f判断
这我们加一个log point
这个我之前视频也分享过
logpoint意思是走到这儿
我们在这儿写什么
它就会在控制台打印什么哈
那我们在这儿让控制台打印
刚才那个方法就是这个setScope
注意不要有分号儿
因为有分号儿它语法就不对了
相当于它是这样了
console.log(……)
所以呢这块不要有分号
回车,然后呢
我们把这个断点都去掉吧
这个去掉
大家看他就可以了刷新
大家看
说明刚才那个方法是可以的
我们再来看一下啊
这块它就有scopeid了
好我们最后总结一下啊
其实这个问题产生的原因呢
就是它这个属性加了之后呢
正好页面和组件之间
产生了这个vnote的共用
导致了它这个没有scopeid
导致样式没有被命中
那解决这个bug呢
我们就直接在有问题的这个div上
或者什么容器上
我们给它加一个key值
保证它跟别人是不冲突的
最后更新于
这有帮助吗?