资讯专栏INFORMATION COLUMN

Vue.js 渲染简写样式存在的问题

dadong / 3467人阅读

摘要:首先将不存在于中的的样式设置为然后再设置与中样式值不相等的的样式看起来没什么问题,一切都很符合逻辑,那么是什么造成了上面的现象呢一切的罪魁祸首都在这个样式的简写属性上。

引出问题

首先我们来这么一个问题, 这里是完整的 jsfiddle demo or codepen demo

给一个元素绑定两个边框样式, 右侧和底部都为1px的红色边框

        styleA: {
          borderBottom: "1px solid red",
          borderRight: "1px solid red"
        };

然后用一个按钮(或者任何方式)将样式换成下面的样式, 一个1px的绿色边框,和1px的红色右侧边框。

        styleB: {
          border: "1px solid green",
          borderRight: "1px solid red"
        };

我们期望的结果应该是右侧边框是红色的,其余三边的边框是绿色的,但实际结果却是所有边都是绿色的, 这里已经出现了问题, 然后再点击按钮,将样式切换回去, 此时期望的结果应该是跟一开始一样: 右侧和底部都为1px的红色边框, 但实际结果却是只剩下底部的边框是红色的,右侧的边框就像消失了一样。

那么, 右侧的边框样式是不是真的消失了呢? 是不是从第一次切换就消失了呢?(这好像也能符合第一次全都是绿色边框的表现),是CSS的bug吗?

这个style的替换过程是在Vue里帮我们实现的,是跟虚拟节点vNode的渲染有关,接下来让我们去Vue的源码看一下这个问题到底是怎么样造成的。

Vue更新视图机制

首先,vue视图的更新通过updateComponent进行, updateComponent会执行一个update的方法进行更新视图,update会从根节点进行patch操作, patch操作会依次遍历虚拟节点树的所有vnode节点,深度优先的遍历方式。

通常patch操作会update以下几个部分

     0: ƒ updateAttrs(oldVnode, vnode)  
     1: ƒ updateClass(oldVnode, vnode)
     2: ƒ updateDOMListeners(oldVnode, vnode)
     3: ƒ updateDOMProps(oldVnode, vnode)
     4: ƒ updateStyle(oldVnode, vnode)
     5: ƒ update(oldVnode, vnode)
     6: ƒ updateDirectives(oldVnode, vnode)

这里我们只需要关注第5个方法:updateStyle, 那么这个方法里做了什么呢?
看一下核心逻辑:

可以看到这段代码的主要逻辑是用新的样式覆盖旧的样式,这里的setProp是对element.style进行修改,也就是原生CSSStyleDeclaration对象的实例。

首先将不存在于newStyle中的oldStyle的样式设置为"",

然后再设置与oldStyle中样式值不相等的newStyle的样式,

看起来没什么问题,一切都很符合逻辑,那么是什么造成了上面的现象呢?

一切的罪魁祸首都在这个border样式的简写属性(shorthand property)上。

简写属性有什么特殊的地方呢?
最直接的就是当对一个简写属性赋值,例如:

border: 1px solid green;

这个赋值会被转换为:

    borderWidth: "1px"
    borderStyle: "solid"
    borderColor: "green"
    
    borderTop: "1px solid green"
    borderTopColor: "green"
    borderTopStyle: "solid"
    borderTopWidth: "1px"
    
    borderRight: "1px solid green"
    borderRightColor: "green"
    borderRightStyle: "solid"
    borderRightWidth: "1px"
    
    borderLeft: "1px solid green"
    borderLeftColor: "green"
    borderLeftStyle: "solid"
    borderLeftWidth: "1px"
    
    borderBottom: "1px solid green"
    borderBottomColor: "green"
    borderBottomStyle: "solid"
    borderBottomWidth: "1px"

也就是说borderTop, borderLeft, borderRight, borderBottom也都被赋值了.

原因分析

所以,回到上面的那个切换过程,根据updateStyle源码进行分析:

styleA切换为styleB时,

第一个for循环, borderBottom不在 oldStyle 中,被清空,borderRight在 oldStyle 中,保留了下来。

第二个for循环, border不在 oldStyle 中,设置border的值,注意此时borderTop, borderLeft, borderRight, borderBottom也都被赋值了,然后borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值。

最后的结果就是 borderTop, borderLeft, borderRight, borderBottom都显示 border的值。

styleB切换回为styleA时,

第一个for循环, border不在 oldStyle 中,border的值被清空,此时borderTop, borderLeft, borderRight, borderBottom也都被清空,然后borderRight在 oldStyle 中, 跳过这次赋值。

第二个for循环, borderBottom不在 oldStyle 中,borderBottom被赋值,borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值

最后的结果也就是只剩下了borderBottom的值。

解决方案

那么,原理搞清楚了,有什么好的解决方案呢? 这个问题在Vue的github上已经被提过issue了,看下尤雨溪的官方回复

这个问题被定性为了一个wontfix,但也给出了有效的解决方案:

给这个元素一个用样式生成的hash值作为key, 当样式有任何变化的时候,key就会变化,在Vue的更新渲染逻辑中,如果元素的key发生变化,那么oldstyle就是空对象,就不会出现上面的问题了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98957.html

相关文章

  • Vue.js 渲染简写样式存在问题

    摘要:首先将不存在于中的的样式设置为然后再设置与中样式值不相等的的样式看起来没什么问题,一切都很符合逻辑,那么是什么造成了上面的现象呢一切的罪魁祸首都在这个样式的简写属性上。 引出问题 首先我们来这么一个问题, 这里是完整的 jsfiddle demo or codepen demo 给一个元素绑定两个边框样式, 右侧和底部都为1px的红色边框 styleA: { ...

    CoderStudy 评论0 收藏0
  • 学习Vue.js-Day1

    摘要:学习内容,基本语法和概念,打包工具,实战操作参考文献官网官方资料库全家桶全家桶文档概念前端框架借助可以实现手机开发前端框架是一套构造用户界面的框架,只关于视图层前端的主要工作室跟用户界面打交道,中的,实现界面效果框架是为了提高开发 学习内容 1,Vue基本语法和概念 2, 打包工具 Webpack , Gulp3,实战操作 参考文献:官网: https://cn.vuejs.org...

    Cheriselalala 评论0 收藏0
  • 学习Vue.js-Day1

    摘要:学习内容,基本语法和概念,打包工具,实战操作参考文献官网官方资料库全家桶全家桶文档概念前端框架借助可以实现手机开发前端框架是一套构造用户界面的框架,只关于视图层前端的主要工作室跟用户界面打交道,中的,实现界面效果框架是为了提高开发 学习内容 1,Vue基本语法和概念 2, 打包工具 Webpack , Gulp3,实战操作 参考文献:官网: https://cn.vuejs.org...

    Cristic 评论0 收藏0

发表评论

0条评论

dadong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<