看vue源碼解決組件style更新問題

最近在項目碰到了一個vue組件更新致使style異常的問題。下面記錄一下我本身的解決思路。javascript

問題背景

因爲公司項目業務複雜,就不具體描述了。簡單說一下問題,就是項目使用vue框架,在一個頁面中根據a值來顯示不一樣組件,當a = true時顯示A組件,不然就顯示B組件。示例代碼以下css

<template>
    <div>
        <div v-if="a" :style="getBackground('a')">a組件</div>
        <div v-else :style="getBackground('b')">b組件</div>
    </div>
</template>

<script>
    export default {
        name:'Example',
        data: {
            a: false
        },
        computed: {
            getBackground: function(type) {
                return {
                    background: `url(https://${type}.png) no-repeat`,
                    backgroundSize: '100% 100%',
                }
            }
        }
        mounted() {
            setTimeout(() => { this.a = true }, 1000)
        }
    }
</script>
複製代碼

問題描述

如上代碼,頁面加載時,顯示 a組件,且它的背景樣式是設置了backgroundImage和backgroundSize爲"100% 100%",一秒以後,a 變爲false了,這是顯示 b組件,預期之中,它也是應該設置了backgroundImage和backgroundSize爲"100% 100%",可是呢,在顯示 b組件,它的樣式,backgroundSize並非"100% 100%",而是默認的"initial",這樣致使樣式並不是咱們預期想要的。究竟爲何在顯示 b組件 時,這個backgroundSize不是咱們在getBackground中返回的100%呢?html

分析問題

爲何顯示 b 組件 時樣式不是咱們預期的呢,這裏,能夠看到 a組件b組件 都是 div標籤,根據vue官方文檔描述,它們在更新時會被複用的,就是說只會建立 a組件 的div元素,在更新b組件時,會複用 a組件 建立出來的div元素的。而且翻看了vue更新組件部分源碼,也確實會先判斷是不是相同的元素類型,若是是,就只是更新,而不會從新建立。可是,就算是複用,那也不該該把backgroundSize覆蓋了"initial"呀?況且這2個組件都設置的backgroundSize是"100% 100%"。vue

接着,我又翻看了更新style部分的源碼才發現了緣由出在哪。下面貼出vue更新stye部分的源碼以下java

// 獲取待更新vnode的style綁定值
 const newStyle = getStyle(vnode, true)

 // 若是在舊的vnode中且不在新的vnode的style中,則刪除
 for (name in oldStyle) {
   if (isUndef(newStyle[name])) {
     setProp(el, name, '')
   }
 }
 // 若是在新的vnode中,且不等於舊的vnode中值,則更新爲新的vnode中style值
 for (name in newStyle) {
   cur = newStyle[name]
   if (cur !== oldStyle[name]) {
     // ie9 setting to null has no effect, must use empty string
     setProp(el, name, cur == null ? '' : cur)
   }
 }
複製代碼

源碼邏輯很簡單,就是先刪除了在舊的vnode中style而不在新的vnode中style的值,接着設置在新的vnode中且不等於舊的vnode中值的。結合上面咱們問題代碼,邏輯應該是,node

  1. background存在 a組件b組件 中,可是值不相等,應該被更新,
  2. backgroundSize存在 a組件b組件 中,值相等,不更新

這樣一來,因爲 a 組件b組件 是複用的同一個div元素,咱們再來具體看一下div元素style 被更新的過程,git

  1. 先是在 a組件 中,div被設置的應該是以下樣式github

    div {
        background: "url(https://a.png) no-repeat",
        backgroundSize: '100% 100%',
    }
    複製代碼

    咱們知道,只設置background的話,它的backgroundSize默認值是"initial",可是後面的backgroundSize會覆蓋background 中默認值,因此這時沒有毛病,顯示正常web

  2. 接着,更新爲 b組件 了,div被設置的樣式應該以下框架

    div {
        //background: "url(https://a.png) no-repeat", //a組件中設置樣式
        backgroundSize: '100% 100%', //a組件中設置樣式
        background: "url(https://b.png) no-repeat", //b組件中設置樣式    
    }
    複製代碼

    這個時候,咱們發現,實際上,設置的background會用默認值"initial"覆蓋掉以前a組件中設置的backgroundSize的"100% 100%",因此這個時候,在顯示 b組件 時,backgroundSize變爲了默認值"initial"。坑爹呀,😢。

解決問題

知道問題是出如今組件複用和background設置順序問題上,那麼解決的辦法就很是簡單了,

  1. 方法一就是給 a 組件b 組件 設置不一樣的key,這樣就不會複用,也不會出現上面的問題了,可是呢,感受跟vue遵循的複用原則相違背,性能也會有所損失(咱們就是要追求極致😂)。
  2. 方法二就是設置background時直接使用backgroundImage而不是background,由於設置background會附帶設置了其餘一些背景相關的css樣式值,實際上background是一系列背景樣式值的簡寫,

The property is a shorthand that sets the following properties in a single declaration: background-clip, background-color, background-image, background-origin, background-position, background-repeat, background-size, and background-attachment.

總結

就業務背景而言,業務上是不可能出現頁面內a會變化的,也就是說,用戶打開頁面,那麼頁面根據a來選擇顯示哪一個組件,以後是不會變的。可是就有某種特殊狀況下,a在頁面未刷新狀況下,變化了,致使更新爲顯示另外一個組件了。本身在作業務需求時,代碼邏輯必定要多加嚴謹,同時要深刻理解框架的底層實現原理,才能更好的避免未知bug。

就這個bug而言,應該有三個基礎知識點:

  1. css規則中,後面的會覆蓋前面的
  2. background等其實是一系列css規則的簡寫
  3. vue中組件複用以及高效更新style邏輯
相關文章
相關標籤/搜索