如何覆蓋元件庫樣式?React和Vue專案的解決方法淺析

2022-05-16 13:00:33
如何覆蓋元件庫樣式?下面本篇文章給大家介紹一下React和專案中優雅地覆蓋元件庫樣式的方法,希望對大家有所幫助!

元件庫的樣式覆蓋不掉,這應該是很多前端在工作中遇到過的問題。今天從實際案例出發分析原因,最後會給出在React和Vue專案中的最優解。

本文會講清:

  • React中CSS Module的原理是什麼?:global是做什麼的?

  • Vue中Scoped的原理是什麼?深度作用選擇器是什麼?(學習視訊分享:)

先不講概念,直接從需求出發:我使用了Antd元件庫來展示一個日曆。

1.png

現在我想將當前日期上面的藍色邊框變成紫色。

可以試試你能不能實現。

不管是React還是Vue,整個Calendar是被封裝起來的,我們沒有辦法在元件外簡單加上style/class改動內部的樣式。

import { Calendar } from 'antd';
...
<div className="myWrapper">
  <Calendar class="custom"/>
</div>

定位要覆蓋的樣式


首先用開發者工具定位對應的樣式:.ant-picker-calendar-date-today,這就是我們要修改的地方。

2.png

.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today {
    border-color: #1890ff; 
}

熟悉webpack的人應該知道,引入的CSS檔案最終都會被style-loader處理。簡單來說,它的作用就是把CSS檔案打包,放在style標籤內,最後塞進HTML中作為一個內部樣式表。不管是元件庫的樣式還是我們寫的自定義樣式都是這樣處理的。

3.png

我們要把元件庫的樣式先於自定義樣式引入,這樣自定義樣式才能有更高的優先順序。

修改原始檔


直接改元件庫的CSS原始碼是最簡單粗暴的方法。開啟你專案的node_modules資料夾,一層層點開,找到對應樣式檔案,按照需求修改即可。

個人專案這樣處理確實可行,但是團隊合作時,同步別人原生的node_modules就比較麻煩,只能算一個60分解法。

全域性CSS檔案


之前提到,把自己寫的的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 Module和Scoped


上面我們引入自定義的全域性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元件,覆蓋也就不會成功。

4.png

所以,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;
}

5.png

藉助: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元素都沒有該屬性。

6.png

所以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>

7.png

藉助深度作用選擇器,可以將要用於覆蓋CSS「滲透」進元件內部。

也可以將>>>寫成/deep/或者::v-deep

相較於React的:global,Vue的深度作用選擇器是一種更優秀的方案,它必須要一個前導(也就是上面例子中的.myWrapper選擇器),前導依舊會被打上雜湊值作為屬性選擇器,要滲透進去的樣式實際上是作為它的子選擇器,只在當前這個檔案下生效,徹底避免造成全域性汙染。

結語

本文通過如何修改UI元件內部樣式為切入點,分析了幾種解法。瞭解了組合選擇器的優先順序分數累加,以及在實際React、Vue專案用到的樣式隔離方案——CSS Module和Scoped的原理,最後是介紹了在樣式隔離的情況下,如何使用:global和深度作用選擇器做樣式覆蓋。

如果這篇文章對你有幫助,給我點個贊和在看吧~

(學習視訊分享:、)

以上就是如何覆蓋元件庫樣式?React和Vue專案的解決方法淺析的詳細內容,更多請關注TW511.COM其它相關文章!