一種比css_scoped和css_module更優雅的避免css命名衝突小妙招

来源:https://www.cnblogs.com/hiisea/archive/2022/09/01/16645688.html
-Advertisement-
Play Games

我們來回憶一下,在`css_scoped`和`css_module`出現之前,人們是如何避免css命名衝突的?對,就是人為的定義一些`css命名空間`。那個時候,對每個Component組件都會在其根節點上定義一個不重覆的ID或者class作為其**命名空間**,然後其內部的其它class都會以此命... ...


css_scoped 與 css_module

我們知道,簡單的class名稱容易造成css命名重覆,比如你定義一個class:

<style>
.main { float: left; }
</style>

如果別人剛好也定義了一個className:.main,你的float:left就會影響到它。

所以Vue中發明瞭css_scoped,其原理就是在class名稱後加上一個data屬性選擇器:

<style scoped>
.main { float: left; } 
</style>

//轉義後變成
<style>
.main[data-v-49729759] { float: left }
</style>

css_scoped是Vue的專用方案,如果你使用React等其它UI框架,那麼你可以使用更通用的css_module,其原理是為樣式名加hash字元串尾碼,從而保證class名全局唯一:

<style module>
.main { float: left; } 
</style>

//轉義後變成
<style>
.main_3FI3s6uz { float: left; } 
</style>

相比於css_scopedcss_module方案更通用,不改變其本身的權重,而且渲染性能要比前者好很多,所以更推薦大家使用css_module

不足之處

然而不管是css_scoped還是css_module,都繞不開2大缺點:

  1. 由於加上了隨機字元,所以如果想在父組件中覆蓋子組件中的樣式變得麻煩,雖然css_scoped可以使用穿透,但這樣容易引發別的問題。
  2. 加上隨機字元讓class名稱變得不優雅,也影響編譯速度。

css命名空間

我們來回憶一下,在css_scopedcss_module出現之前,人們是如何避免css命名衝突的?

對,就是人為的定義一些css命名空間

那個時候,對每個Component組件都會在其根節點上定義一個不重覆的ID或者class作為其命名空間,然後其內部的其它class都會以此命名空間作為前置限定,比如:

<div class="table-list">
    <div class="hd"></div>
    <div class="bd"></div>
    <div class="ft"></div>
</div>

<style>
.table-list {
    > .hd {
        color: red
    }
    > .bd {
        color: blue
    }
} 
</style>

這樣一來,只要保證根節點的class不重覆,其子節點的class就不會重覆。

而對於一些全局樣式,人們習慣加上一個g-作為命名空間,比如:

<style>
.g-hd {
    color: red
} 
</style>

這種依靠人為約定的css命名空間,雖然比較原始,但有其優點:

  • 簡單有效,按模塊-組件名稱的命名約定,基本上很容易保證其不重覆。
  • 樣式名更具語義,從任何一個dom出發,向上一定能找到其組件根節點class名,基本上就能猜到其組件所在的業務模塊、組件位置等。
  • 父組件很容易利用權重覆蓋子組件的任何樣式。

css_namespace + css_module

如果我們把css_modulecss_命名空間結合起來,組件的命名空間由css_module自動生成,那豈不是一種更優雅的解決css衝突的方案麽?

css_module中有2個特別的作用域限定符:

  • :global 該限定符下的class名稱將保持原樣,不會被css moudle轉換,比如:
    :global { 
        .test1 { color: blue; } 
        .test2 { color: red; } 
    }
    //編譯後
    .test1 { color: blue; }
    .test2 { color: red; } 
    
  • :local 該限定符下的class名稱,將會被css moudle轉換,比如:
      :local { 
          .test1 { color: blue; } 
          .test2 { color: red; } 
      }
      //編譯後
      .test1_3zyde4l1y { color: blue; }
      .test2_2DHwuiHWM { color: red; } 
    

如果我們使用css_namespace + css_module

<div :class="styles.root">
    <div class="hd"></div>
    <div class="bd"></div>
    <div class="ft"></div>
</div>

<style module>
:global {
  :local(.root) {
    > .hd { 
     color: red;
     .title {
         font-size: 18px;
     }
    }
    > .bd { color: blue; }
  }
}
</style>

//css編譯後
<style>
.root_3zyde4l1y > .hd{ color: red; }
.root_3zyde4l1y > .hd .title{ font-size: 18px; }
.root_3zyde4l1y > .bd{ color: blue; }
</style>

這樣的意思是:

  • 每個組件原則上僅根節點使用css_module自動生成不重覆的class名稱,其餘內部元素保持原始命名,不做任何轉換。(當然某些情況下,也可以使用多個轉換)
  • 為了保證孫子輩樣式不影響別人,可以適當加入dom層級限定,比如> .hd這樣就只會影響子級的.hd

去除css_moudle隨機字元

<style>
.root_3zyde4l1y > .hd{ color: red; }
.root_3zyde4l1y > .hd .title{ font-size: 18px; }
.root_3zyde4l1y > .bd{ color: blue; }
</style>

根節點上的class命名帶個hash小尾巴,仍然很不優雅。其實hash字元只是為了保證這個名稱全局唯一而已,你也可以使用另外的方法來保證。如果你為工程設計一個有意義的目錄結構,那麼完全可以使用目錄路徑來替代hash字元串,比如你的工程目錄如下:

src
├── components
│    ├── moduleA
│    │     ├── componentX
│    │     ├── componentY
│    ├── moduleB
│    │     ├── componentZ

那麼:components-moduleA-componentX這個目錄路徑一定是全局唯一的,所以你可以使用這個路徑來替代hash字元,css_module提供了自定義轉換className的方法:

type getLocalIdent = (
        context: LoaderContext,
        localIdentName: string,
        localName: string
) : string;

你可以通過該方法來將目錄路徑映射為class名稱,並替換掉一些固定的目錄,比如工程目錄如下:

src
├── assets
│     ├── css
│           ├── global.module.scss //全局樣式
│                  ├── :local(.loading) {} //全局樣式只需要加個g-首碼,編譯成.g-loading
├── components
│     ├── NavBar
│           ├── index.module.scss
│                  ├── :local(.root) {} //根據目錄路徑可編譯成即可.comp-NavBar
│
├── modules
│     ├── user
│           ├── components
│                 ├── LoginForm
│                         ├── index.module.scss
│                               ├── :local(.root) {} //根據目錄路徑可編譯成.user-LoginForm,
│

註意的是src/modules/user/components/LoginForm/index.module.scss,根據目錄路徑可以生成:modules-user-components-LoginForm,但因為user是一個module,其名稱是唯一的,且內部結構遵循約定,所以可以簡化為:user-LoginForm

根據class名稱推測文件位置

  • .g-loading - 帶g-首碼,說明它是一個全局class,對應的文件一定是src/assets/css/global.module.scss
  • .comp-NavBar - 帶comp-首碼,說明它是一個公共組件,對應的組件一定是src/components/NavBar
  • .user-LoginForm - 根據約定,對應的組件一定是src/modules/user/components/LoginForm

示例及源碼

如果你也使用類似的工程目錄,那麼可以直接使用我封裝好了的路徑映射函數getCssScopedName

const {getCssScopedName} = require('@elux/cli-utils');
const srcPath = path.resolve(__dirname, '../src');

// webpack css-loader
{
    loader: 'css-loader',
    options: {
      importLoaders: 2,
      modules: {
        getLocalIdent: (context, localIdentName, localName) => {
          return getCssScopedName(srcPath, localName, context.resourcePath);
        },
        localIdentContext: srcPath,
      },
    },
  };

當然你也可自己實現個性化的getLocalIdent,無非就是一些正則匹配與替換罷了...

採用css_namespace + css_module的實際案例:

111.jpg

如圖所示,通過class名稱基本上就能推測出組件位置...


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目來源於前端面試寶典——選擇題欄 習題 class Counter { // 二、count 屬性被包含在類 Counter 的構造函數與 increment 方法。 constructor() { this.count = 0 } increment() { this.count++ } } / ...
  • 代理模式 1 定義 為其他對象提供一種代理以控制對這個對象的訪問 在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。 2 應用舉例 2.1 緩存代理 現在我們有一個可以查詢城市經緯度的函數: const getLatLng = (addres ...
  • 1 含義 頁面導航就是指頁面之間的一個跳轉 1.1 導航的實現方式 Web網頁端 微信小程式 1.2 聲明式導航 1.2.1 導航到tabBar頁面 使用<navigator>組件跳轉到指定的tabBar頁面 url:以/開頭,標識要跳轉的頁面地址 open-type:必須為switchTab,表示 ...
  • 作為一名H5開發人員可以使用hbuilder或apicloud。但是,到打包和發佈時,被申請ios證書和上架ipa文件給了困難。由於官方提供的方法,申請證書需要使用Mac電腦然後使用Mac電腦中的密鑰鏈訪問,去申請證書csr文件。然後再去蘋果開發者中心申請。 但是,Mac或未使用的Mac朋友都被 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 小伙伴們,在開發中有沒有遇到過發佈帖子或者實時聊天需要發送到一些emoji表情的。 但是每當我們直接將emoji表情提交到後臺的介面又會報錯,因為字元串的格式不一致導致數據無法添加致資料庫。那麼作為前端的我們就要將我們要提交的數據,通過如 ...
  • 自定義過渡的類名 點擊打開視頻講解更加詳細 我們可以通過以下 attribute 來自定義過渡類名: enter-class enter-active-class enter-to-class (2.1.8+) leave-class leave-active-class leave-to-clas ...
  • 前言 前段時間一時興起想學一下吉他,但是一門樂器要演奏成“能聽”的程度也不是一天兩天的事情,對我這種音樂基礎為 0 的人來說學習周期太長了,不想耗費太多時間在學習樂器上面,於是想找個取巧的方法。 最終方案就是做了個簡單粗陋的微信小程式 Demo 去彈奏吉他樂,勉強算是成功吧,可以很簡單地彈奏出樂曲。 ...
  • 每日 3 題 7 以下代碼執行後,控制臺中的輸出內容為? const obj = { flag: false, }; function A() { this.flag = true; return obj; } const a = new A(); console.log(a.flag); 8 以下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...