zoukankan      html  css  js  c++  java
  • Vue.js 渲染简写样式存在的问题

    引出问题

    首先我们来这么一个问题, 这里是完整的 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, 那么这个方法里做了什么呢?
    看一下核心逻辑:

    clipboard.png

    可以看到这段代码的主要逻辑是用新的样式覆盖旧的样式,这里的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时,

      1. 第一个for循环, borderBottom不在 oldStyle 中,被清空,borderRight在 oldStyle 中,保留了下来。
      2. 第二个for循环, border不在 oldStyle 中,设置border的值,注意此时borderTop, borderLeft, borderRight, borderBottom也都被赋值了,然后borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值。
      3. 最后的结果就是 borderTop, borderLeft, borderRight, borderBottom都显示 border的值。
    • styleB切换回为styleA时,

      1. 第一个for循环, border不在 oldStyle 中,border的值被清空,此时borderTop, borderLeft, borderRight, borderBottom也都被清空,然后borderRight在 oldStyle 中, 跳过这次赋值。
      2. 第二个for循环, borderBottom不在 oldStyle 中,borderBottom被赋值,borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值
      3. 最后的结果也就是只剩下了borderBottom的值。

    解决方案

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

    clipboard.png

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

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

    原文地址:https://segmentfault.com/a/1190000016895358

  • 相关阅读:
    【PHP】php重写session的存储机制
    【Javascript】原生js 全特效微博发布面板效果实现
    【Javascript】原生js百叶窗效果的实现及原理介绍
    【jQuery】jquery全屏滚动插件【fullPage.js】API 使用方法总结
    从百度音乐和酷狗音乐的分类想到的
    jQuer __Ajax DOM
    面向对象闭包 继承
    Git
    设计模式
    html5标签大全
  • 原文地址:https://www.cnblogs.com/lalalagq/p/9943513.html
Copyright © 2011-2022 走看看