元件庫的樣式覆蓋不掉,這應該是很多前端在工作中遇到過的問題。今天從實際案例出發分析原因,最後會給出在React和Vue專案中的最優解。
本文會講清:
React中CSS Module的原理是什麼?:global
是做什麼的?
Vue中Scoped的原理是什麼?深度作用選擇器是什麼?(學習視訊分享:)
先不講概念,直接從需求出發:我使用了Antd元件庫來展示一個日曆。
現在我想將當前日期上面的藍色邊框變成紫色。
可以試試你能不能實現。
不管是React還是Vue,整個Calendar是被封裝起來的,我們沒有辦法在元件外簡單加上style/class改動內部的樣式。
import { Calendar } from 'antd'; ... <div className="myWrapper"> <Calendar class="custom"/> </div>
首先用開發者工具定位對應的樣式:.ant-picker-calendar-date-today
,這就是我們要修改的地方。
.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today { border-color: #1890ff; }
熟悉webpack的人應該知道,引入的CSS檔案最終都會被style-loader處理。簡單來說,它的作用就是把CSS檔案打包,放在style標籤內,最後塞進HTML中作為一個內部樣式表。不管是元件庫的樣式還是我們寫的自定義樣式都是這樣處理的。
我們要把元件庫的樣式先於自定義樣式引入,這樣自定義樣式才能有更高的優先順序。
直接改元件庫的CSS原始碼是最簡單粗暴的方法。開啟你專案的node_modules資料夾,一層層點開,找到對應樣式檔案,按照需求修改即可。
個人專案這樣處理確實可行,但是團隊合作時,同步別人原生的node_modules就比較麻煩,只能算一個60分解法。
之前提到,把自己寫的的CSS檔案放在元件庫的樣式後面,可以保障自定義有更高優先順序。只要重寫同名的樣式,理論上就能實現覆蓋組了。
但這樣?處理會發現並不起作用:
/* src/demo.css */ .ant-picker-calendar-date-today { border-color: purple; /* 覆蓋為紫色 */ }
// src/Demo.js // 元件庫的樣式 import 'ant-design-vue/dist/antd.css'; // 自定義樣式 import './demo.css' import { Calendar } from 'antd'; ... <div className="myWrapper"> <Calendar /> </div> ...
因為這裡還涉及CSS組合選擇器的優先順序。
基礎的優先順序應該不用贅述:!important>內聯樣式>ID選擇器>類選擇器>標籤選擇器
。(!important這種hack會導致專案不好維護,不提倡使用)
在這個基礎上還有五種組合選擇器要對優先順序分數做累計,以類選擇器為例:
後代選擇器(空格):.A .B
選擇.A元素後的所有.B元素,
子元素選擇器(大於號):.A>.B
選擇.A元素的直接後代中的.B元素
相鄰兄弟選擇器(加號):.A+.B
選擇.A元素後緊鄰的第一個兄弟.B元素
後續兄弟選擇器(~號):.A~.B
選擇.A元素後所有的兄弟.B元素
交集選擇器(連在一起):.A.B
選擇自身同時擁有.A和.B兩個屬性的元素
上面幾個規則看著很複雜,其實用的多的就是第一個後代選擇器,記住它就行。Antd元件庫用的就是它:
.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today { border-color: #1890ff; }
如果說一個類選擇器優先順序分數是10分,那三個形成的後代選擇器就是30分。
而自定義的樣式?只有10分,所以即使放在更後面引入,也不能成功覆蓋。
.ant-picker-calendar-date-today { border-color: purple; // 覆蓋為紫色 }
需要完整重寫整個選擇器才能實現想要的效果。
這裡補充一點,同樣也是組合選擇器,但並集選擇器(逗號)優先順序不累計:
.A, .B
選擇.A或者.B元素(可以是逗號+空格)
上面我們引入自定義的全域性CSS檔案,實現了樣式的覆蓋,但是這種解法只能給80分。因為在實際工作中,專案Owner通常不允許使用全域性CSS,這會造成樣式汙染:你定義了一個樣式my_button
,團隊其他人恰巧也命名為my_button
,這就造成樣式衝突。
我們需要給每個檔案做樣式隔離,就好像是給它一個名稱空間。通常使React專案使用的是用的是CSS Module,Vue專案使用Scoped標記。
接下來會講清兩種樣式隔離的原理,以及使用樣式隔離時怎麼覆蓋元件庫的樣式。
React的CSS Module
首先來了解一下CSS Module的原理。它的使用很簡單,在CSS檔案加一個字尾.module
,然後當做一個變數引入到JS檔案中。
// src/Demo.js import styles from './demo.module.css'; export default function Demo() { return ( <div className={styles.myWrapper}> <Calendar /> </div> ); }
/* src/demo.module.css */ .myWrapper { border: 5px solid black; }
被編譯後?,插入的樣式表和元素的class屬性都會加上一個雜湊值作為名稱空間。
<style> .demo_myWrapper__Hd9Qg { border: 5px solid black; } </style> <div class="demo_myWrapper__Hd9Qg"> ... </div>
可以看到,原本的CSS選擇器和HTML元素類名都從myWrapper
變成了demo_myWrapper__Hd9Qg
,前面加上了檔名,後面加上了雜湊值,這樣就能保障樣式只在當前這個檔案下生效了。
但是在這種樣式隔離情況下,我們原本用作覆蓋的CSS也被加上了雜湊值,就像下圖這樣,這時沒有辦法選中UI元件,覆蓋也就不會成功。
所以,React給我們提供了一個語法:global
。它生效範圍內的樣式會被當作全域性CSS。
具體使用如下,在CSS檔案中,使用:global
包裹希望全域性生效的樣式
:global(.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today) { border-color:purple; /* 覆蓋為紫色 */ }
SCSS或SASS中,還可以使用巢狀語法:
:global { .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today { border-color:purple; } }
最後編譯出來的程式碼如下:
/* 加上了雜湊*/ .demo_myWrapper__Hd9Qg { border: 5px solid black; } /* :global作用域下都不會加上雜湊*/ .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today { border-color:purple; }
藉助:global
語法,即使使用CSS Module進行樣式隔離也可以如願實現覆蓋功能。
Vue中的Scoped
Vue中也有類似的樣式隔離功能,使用Scoped標記CSS部分,使用也很簡單?:
<style scoped> .myWrapper{ border: 5px solid black } </style> ... <div class="myWrapper" > <Calendar /> </div> ...
編譯出來的程式碼如下:
<style> .myWrapper[data-v-2fc5154c] { border: 5px solid black } </style> <div class="myWrapper" data-v-2fc5154c> ... </div>
可以看到,它的原理和CSS Module不太一樣,Vue的Scoped會使CSS選擇器後加上一個中括號。
這並不是Vue獨創的語法,而是屬性選擇器。.myWrapper[data-v-2fc5154c]
代表選擇擁有data-v-2fc5154c這個屬性的、同時是myButton類的HTML元素。只有這個檔案內部的HTML元素才會被打上data-v-2fc5154c這個屬性。其餘檔案的HTML元素即使是myWrapper類,這個樣式也不會對他生效。
回到相同的問題,假如Vue專案在使用了Scoped做樣式隔離,我們用於覆蓋的樣式也會加上屬性選擇器,但是UI元件內部的HTML元素都沒有該屬性。
所以Vue提供了一個類似的語法:深度作用選擇器。
使用很簡單,把要「滲透「進元件內部的樣式前面加上>>>
,作用域內的CSS樣式都不會帶上雜湊值作為屬性選擇器。
<style scoped> .myWrapper>>> .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today{ border-color:purple } </style> <template> <div class="myWrapper" > <Calendar /> </div> </template>
編譯後
<style> .myWrapper[data-v-2fc5154c] .ant-picker-calendar-full .ant-picker-panel /* 作用域內的CSS都沒有帶上屬性選擇器 */ .ant-picker-calendar-date-today { border-color:purple } </style> <div class="myWrapper" data-v-2fc5154c> <div class="ant-picker-calendar-full" data-v-2fc5154c> <div class="ant-picker-date-panel"> <td class="ant-picker-cell-today"></td> </div> </div> </div>
藉助深度作用選擇器,可以將要用於覆蓋CSS「滲透」進元件內部。
也可以將
>>>
寫成/deep/
或者::v-deep
。
相較於React的:global
,Vue的深度作用選擇器是一種更優秀的方案,它必須要一個前導(也就是上面例子中的.myWrapper選擇器),前導依舊會被打上雜湊值作為屬性選擇器,要滲透進去的樣式實際上是作為它的子選擇器,只在當前這個檔案下生效,徹底避免造成全域性汙染。
本文通過如何修改UI元件內部樣式為切入點,分析了幾種解法。瞭解了組合選擇器的優先順序分數累加,以及在實際React、Vue專案用到的樣式隔離方案——CSS Module和Scoped的原理,最後是介紹了在樣式隔離的情況下,如何使用:global和深度作用選擇器做樣式覆蓋。
如果這篇文章對你有幫助,給我點個贊和在看吧~
(學習視訊分享:、)
以上就是如何覆蓋元件庫樣式?React和Vue專案的解決方法淺析的詳細內容,更多請關注TW511.COM其它相關文章!