Vue.js 渲染簡寫樣式存在的問題

引出問題

首先咱們來這麼一個問題, 這裏是完整的 jsfiddle demo or codepen demovue

給一個元素綁定兩個邊框樣式, 右側和底部都爲1px的紅色邊框node

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

而後用一個按鈕(或者任何方式)將樣式換成下面的樣式, 一個1px的綠色邊框,和1px的紅色右側邊框。git

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

咱們指望的結果應該是右側邊框是紅色的,其他三邊的邊框是綠色的,但實際結果倒是全部邊都是綠色的, 這裏已經出現了問題, 而後再點擊按鈕,將樣式切換回去, 此時指望的結果應該是跟一開始同樣: 右側和底部都爲1px的紅色邊框, 但實際結果倒是隻剩下底部的邊框是紅色的,右側的邊框就像消失了同樣。github

那麼, 右側的邊框樣式是否是真的消失了呢? 是否是從第一次切換就消失了呢?(這好像也能符合第一次全都是綠色邊框的表現),是CSS的bug嗎?spa

這個style的替換過程是在Vue裏幫咱們實現的,是跟虛擬節點vNode的渲染有關,接下來讓咱們去Vue的源碼看一下這個問題究竟是怎麼樣形成的。.net

Vue更新視圖機制

首先,vue視圖的更新經過updateComponent進行, updateComponent會執行一個update的方法進行更新視圖,update會從根節點進行patch操做, patch操做會依次遍歷虛擬節點樹的全部vnode節點,深度優先的遍歷方式。code

一般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, 那麼這個方法裏作了什麼呢?
看一下核心邏輯: ip

clipboard.png

能夠看到這段代碼的主要邏輯是用新的樣式覆蓋舊的樣式,這裏的setProp是對element.style進行修改,也就是原生CSSStyleDeclaration對象的實例。element

  • 首先將不存在於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就是空對象,就不會出現上面的問題了。
相關文章
相關標籤/搜索