若是你習慣在Vue裏使用css簡寫屬性,記得避開這個坑

引出問題

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

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

styleA: {
            borderBottom: '1px solid red',
            borderRight: '1px solid red'
	};
複製代碼

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

styleB: {
            border: '1px solid green',
            borderRight: '1px solid red'
	};
複製代碼

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

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

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

Vue更新視圖機制

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

一般patch操做會update如下幾個部分spa

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

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

  • 首先將不存在於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了,看下尤雨溪的官方回覆

This is a wontfix. It's impractical to handle all the possible shorthand variations in the diffing algorithm. The solution is: do not use shorthand properties in inline styles.

If you really have to, e.g. you are allowing the user to edit the css arbitrarily, then the workaround is giving the element in question a key that equivalents to the hash of its inline styles. This forces the element to be replaced fresh when its inline styles change.

這個問題被定性爲了一個wontfix,但也給出了有效的解決方案:

  • 給這個元素一個用樣式生成的hash值做爲key, 當樣式有任何變化的時候,key就會變化,在Vue的更新渲染邏輯中,若是元素的key發生變化,那麼oldstyle就是空對象,就不會出現上面的問題了。
相關文章
相關標籤/搜索