架構師(2021年9月)

卷首語 | 我對項目中css架構的理解和使用

作者:孟笑晨

在實現業務的過程中,我們難免會發現之前由於各種原因存在的代碼中正在產生大量的冗餘。這時候就需要優化代碼,如果有功能的迭代,就是進行重構的好時機了!

在我負責重構公司項目的過程中,越來越意識到“架構思維”的重要性,以及它對於後續維護甚至是新功能迭代的幫助!


ITCSS

這是由csswizardry提倡的一個 CSS 設計方法論,他可以讓你更好的管理、維護你的項目的 CSS。

它可以幫助你

  • 管理 CSS 代碼的書寫順序

  • 通過分層來明確每層 CSS 的作用

  • 更好地使用 CSS cascade(權重)

  • 安全的使用繼承

ITCSS 把 CSS 分成了以下的幾層:

Layer 作用
Settings 項目使用的全局變量
Tools mixin & function
Generic 最基本的設定 normalize.css,reset
Base type selector
Objects 不經過裝飾 (Cosmetic-free) 的設計模式
Components UI 組件
Trumps helper 唯一可以使用 important! 的地方

也就是常說的“七層架構”。

但在實際項目中,我們只需借鑑其思路,達到維護一套完善利於閱讀、擴展、複用的css代碼即可。

我是怎麼做的

首先,上面說的第一層 —— Settings 是很重要的。我們可以在其中放一些公共css變量。比如負責更改主題色的變量。常見的有:

  • 顏色

  • 邊框

  • 字體大小

  • 陰影

  • 層級

  • 排版

/* Color

----------------------- */

$color-primary: #FF5777;

$color-white: #FFFFFF;

$color-black: #000000;

$color-text-primary: #333333;

$color-text-secondary: #666666;

$color-text-tertiary: $color-white;

$background-color-primary: #F1F1F1;

$background-color-secondary: $color-white;

$background-color-tertiary: $color-primary;

/* Border

----------------------- */

$border-width-base: 1Px;

$border-style-base: solid;

$border-base: $border-width-base $border-style-base $border-color-base;


/* z-index

-------------------------- */

$index-normal: 1;

$index-top: 1000;

$index-popper: 2000;

注意:這裏必須是抽取的全局的、多個地方會使用到的公共的樣式變量。更加細節的可以放在後面層級中單獨寫。

但通常,主題色不是這麼容易實現的。它甚至需要大量的函數計算以及js的介入 —— 目前流行Ant Design採用了“三套主題色變量” 的方式(將Settings和Theme合併爲一層);而elementUI是在 Base 層下又加了一層Theme(這也是爲什麼選擇ITCSS方案的原因:隨意擴展和縮減)。

然後是 tools 層,也是不可或缺的。這裏面經常被用來放一些“工具樣式”:比如當你使用了scss後的一些需要全局處理的mixin函數、比如水平垂直居中、比如溢出省略、清除浮動等樣式類或function

關於這一層,網上有好多人推薦_sassMagic.scss庫。據說挺好用的!

值得注意的是:上面兩層都是 全局層面 的。一般筆者是這樣安排的:(在我司的大部分項目中,這兩層都屬於自研腳手架中內置的)

css-1

這裏說一句題外話:其實原生 css 越想寫簡單(提高複用)就越會發現,如果 css 中能引用 css 就好了(非@import形式) —— 這樣 css 的東西就可以在 css 內部解決,完成一次複用。到 html 中只需引入一個類/屬性名即可!scssmixin就達到了這個效果。

@mixin large-text {

	color: #737373;

}

.line-title {

	@include large-text;

	padding: 4px 0;

}

.code-title {

	@include large-text;

	padding: 2px;

}

(css的自定義變量也可以達到這樣的效果 —— 不過你要寫在:root中。而且 scss 中的自定義變量更加強大!)

對這一層感興趣的可以研究elementUI 庫源碼,它的mixin寫的非常之精妙。

注意⚠️:這兩層的主要代碼(涉及創建了 變量、mixinfunction的)是要在vue.config.js中引入的 —— 這樣就能在其它css和頁面中使用到:

module.exports = {

  css: {

    loaderOptions: {

      scss: {

        prependData: `

          @import "@/style/settings/var.scss";

          @import "@/style/theme/scss/index.scss";

          @import "@/style/tools/_sassMagic.scss";

          `

      },

  }

}

第三層 Generic。這一層就是專門放置一些css樣式初始化等功能。你可以選擇normalize.css這樣成型的第三方css;也可以根據項目中用到的標籤做針對性初始化處理。

這一層沒什麼說的。

第四層 Base 層:這一層可以用來放定製化css樣式 —— 它是對基礎樣式的補充。比如你的網站中 a 鏈接點擊後是什麼樣、或者 li 的前面幾個點是什麼樣的。

這兩層是 組件級別 的。

一般來說,“全局級別”的樣式主要負責供應“全局”、“其它低級別樣式文件”以及“極少量獨立樣式代碼”;而“組件級別”主要負責供應所有構成頁面的組件中需要的樣式、制定本項目樣式規範以及特殊情況。

第五層 Object 和第六層 Components 其實可以合併爲一層:component。它其實就是寫組件。

這一層首先在結構上不再維護在和其它目錄同級的目錄下(如上面的style),而是放在組件存放的 components 目錄下。

在這一層你要做的就是:自行/利用第三方庫封裝一個具有“基本架子(結構)”的組件。考慮到複用性,所以這裏使用最多的就是slot了。比如:

<!-- src/components/layout/footer.vue -->

<template>

  <footer class="c-footer">

    <slot></slot>

  </footer>

</template>

<script>

export default {

  name: 'VFooter'

}

</script>

<style lang="scss" scoped>

/** 使用到tools層的mixin:底部固定,且有一個高zIndex */

@include b(c-footer) {

  position: fixed;

  bottom: 0px;

  width: 100%;

}

</style>

由於這一層的“特殊性”,再加上根據 css 中的就近原則來說他們對html的影響是最大的,也是最小的(只負責一個文件的樣式,一般一個文件就是一個部分的功能)。所以推薦OOCSS(面向對象css)的進階寫法:BEM

BEM規範

場景一:開發一個彈窗組件,在現有頁面中測試都沒問題,一段時間後,新需求新頁面,該頁面一打開這個彈窗組件,頁面中樣式都變樣了,一查問題,原來是彈窗組件和該頁面的樣式相互覆蓋了,接下來就是修改覆蓋樣式的選擇器…每次爲元素命名都心驚膽戰

場景二:承接上文,由於頁面和彈窗樣式衝突了,所以把頁面的衝突樣式的選擇器加上一些結構邏輯,比如子選擇器、標籤選擇器,藉此讓選擇器獨一無二。一段時間後,新同事接手跟進需求,對樣式進行修改,由於選擇器是一連串的結構邏輯,看不過來,嫌麻煩,就乾脆在樣式文件最後用另一套選擇器,加上了覆蓋樣式…接下來又有新的需求…最後的結果,一個元素對應多套樣式,遍佈整個樣式文件…

以往開發組件,我們都用“重名概率小”或者乾脆起個“當時認爲是獨一無二的名字”來保證樣式不衝突,這是不可靠的。

理想的狀態下,我們開發一套組件的過程中,我們應該可以隨意的爲其中元素進行命名,而不必擔心它是否與組件以外的樣式發生衝突。

BEM解決這一問題的思路在於,由於項目開發中,每個組件都是唯一無二的,其名字也是獨一無二的,組件內部元素的名字都加上組件名,並用元素的名字作爲選擇器,自然組件內的樣式就不會與組件外的樣式衝突了。

這是通過組件名的唯一性來保證選擇器的唯一性,從而保證樣式不會污染到組件外。

這也可以看作是一種“硬性約束”,因爲一般來說,我們的組件會放置在同一目錄下,那麼操作系統中,同一目錄下文件名必須唯一,這一點也就確保了組件之間不會衝突。

BEM的命名規矩很容易記:block-name__element-name--modifier-name,也就是模塊名 + 元素名 + 修飾器名

這裏面還涉及到一個問題:要不要用scope?這個問題值得深思,比如vue中的scoped會形成一個樣式隔離。如果需要樣式複用還需要樣式穿透的介入,非常麻煩。但是一味的遵循“開放”反而會引來“無妄之災”。

OOCSS中,最重要的便是“結構與皮膚分離”。結構就是指“基礎對象”,也就是我們說的“搭好一個架子”。

<div class="media">

	<div class="m-img"></div>

	<span class="m-content"></span>

</div>

遇到上面的HTML,一般會先給一個“固定的樣子”:

.media {

	.m-img {

	}

	.m-content {

	}

}

這時候如果有新的樣式或者顏色之類的改動。就需要另寫一個類名:

<div class="media m-color">

	<div class="m-img"></div>

	<span class="m-content"></span>

</div>

.m-color {

	color: red;

}

但是在其它組件中,就完全不受影響:

<div class="media">

	<div class="m-img"></div>

	<p class="m-content"></p>

</div>

OOCSS的複用就是體現在“架子的重複使用”上。這一點和“組件複用”有異曲同工之妙!也是這一層的基本思想。

ACSS規範

有時候我們還會在 component 上(樣式的優先級比component低)再加一層:ACSS。

ACSS是原子類樣式。通俗的講就是“一個類只寫一個樣式”。這樣的好處是可以達到對css的極限複用。而不好的地方就是讓css失去了語義化

可以用屬性選擇器解決無語義化的痛點。

在 scss 強大的函數加持下,比如你寫不同透明度的background可以這樣:

/** 背景顏色

[bgaxxx] {

  background-color: rgba(0, 0, 0, 0.xxx);

}

*/

@each $i in 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1 {

  [bga#{$i * 10}] { 

    background-color: rgba(0, 0, 0, $i);

  }

}

像我司這種電商平臺,會有“今日必搶”、“限時搶購”這樣的爲了抓住用戶眼球而區別於一般字體的藝術字存在。而這種字體並不是所有地方都要用到的,而且它只作用於字體樣式。我們就可以在 ACSS 層下新建 font 文件夾實現。

最後一層 Trumps,其實就是在你的業務(組件)中進一步“描述”功能的差異性。結合之前層次樣式的複用展現想要的效果。

爲什麼架構

css架構的目的不是“爲了在頁面不寫css代碼”,而是“爲了更好的複用,更簡單的維護,和更清晰的結構”!

在上面的內容中,很明顯看到:

  • “層級越靠前,優先級越低、複用性越強”

  • “下一層永遠繼承上面(所有)層”

意猶未盡,說點其他的

在只有少量功能迭代的場景下,如果碰上週期不那麼長其實是沒法“推翻重來”的。這時候我們只能做一點點的優化(當然,本文說的都是結構上的,這裏也不例外)。

上面的文字翻來覆去的看,其實核心也就兩個字 —— 複用 。儘量減少重複代碼的編寫、甚至是文件的數量。而這一點在大多數項目中都可以優化:

比如提升層級:有時候剛開始寫就是爲了功能的實現,但是有可能這個 div 下包裹的文字和 div 同級的某個地方的文字是一樣的,不管大小還是family,簡直就是一個 family 的!其實可以將文字抽離出來,作爲單獨的一個樣式類(或者屬性選擇器)。甚至是按照上面的ACSS層規範來,因爲字體這玩意不可能只有一個頁面有。花點時間嘛,哪怕花一點呢!

還比如溢出省略這些效果、可能某些公司業務場景還有css特效。這些不是妥妥的Tools層嗎?

最後,我和某人聊起的時候他說會不會到後面文件(夾)太多。我覺得不會影響大局,它帶來的收益是大於損耗的。而且像我司自研腳手架是確保文件按需加載的,就完全沒有這個擔心了,哈哈。

目錄

熱點 | Hot

終於!RocketMQ 迎來第五個大版本,深度解讀“消息、事件、流”超融合處理平臺

今年你關注 XDR 了嗎?谷歌雲安全和七家公司成立 XDR 聯盟

觀點|Opinion

左耳朵耗子:你得知道,技術不是用來寫 CRUD 的

混沌工程 = 可觀測性 + 探索性測試?

推薦文章 | Article

用盡一切手段降低MTTR,混沌工程在華泰證券的落地實踐

讓 Flutter 在鴻蒙系統上跑起來

專題|Topic

Adobe 將 PB 級數據遷移到 Iceberg 的實踐與經驗教訓

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章