組織css程式碼

RobinsonZhang發表於2019-01-08

前言

本文主要描述的組織css的問題,文章內容整理自《前端架構設計》的讀書筆記。

繼承與特性之爭


h2{
}
#sidebar h2{  
background:red;
}
#sidebar .calendar h2{
background:green;
}

複製程式碼

上面這樣寫樣式有很大的問題,主要是我們在樣式書寫時預設的繼承到了之前的樣式,但在特殊化的樣式時,為了讓自己的樣式生效,又要新寫樣式進行覆蓋。

所以列舉一下可能的問題是下面的:

  • 選擇器的優先順序:選擇合適的優先順序,查詢效率高,方便其他人樣式覆蓋的需求
  • 顏色重置:要恢復到原來的顏色,需要進行不斷的重置或者賦值
  • 位置依賴:如果我們的樣式移到其他位置,那麼樣式程式碼就會失效,因為嚴格依賴dom的結構和順序,不能很好的解耦
  • 多重繼承:最終元素的樣式可能是層層巢狀得到的最終樣式程式碼,非常不可複用,當改變主體的樣式時,子元素都會受到影響
  • 深層巢狀:效率非常低

現代化的模組方案

在之前的章節中,我們講到了css不同的模組化方案,那麼對於上面描述的問題,其實可以很簡化的去解決。

案例程式碼

你根據以上的需求按照模組的方案和命名,去除各種依賴,最大程度使用class命名方式,減少繼承,更大程度的使用獨立樣式程式碼塊,輕鬆解決了以上的問題。

<h2 class="content_title"></h2>
<div class="sidebar">
    <h2 class="content_title--reversed"></h2>
    <div class="calendar">
         <h2 class="calendar_title"></h2>
    </div>
</div>
複製程式碼
// 元件資料夾
.content_title{}
// 不用恢復重置樣式
.contitle_title--reverse{}
// 特殊元件的title定義
.calendar_title{
}
複製程式碼

單一職責原則

比如我們在定義標題的時候,可能有兩個模組都用到了標題。

<div class="calendar">
 <h2 class="primary-title"></h2>
</div>
<div class="blog">
 <h2 class="primary-title"></h2>
</div>
<style>
.primary-title{
}
</style>
複製程式碼

可能的問題,是我們需要在日曆或者部落格的時候需要去修改這個樣式,於是你的樣式可能變成這樣的。

.primary-title{
}
.blog .primary-title{}
複製程式碼

這樣的問題是會導致選擇器過多,於是我們按照bem進一步優化。

.calendar_header{}
.blog_header{}
複製程式碼

這樣維護之後,每個樣式塊都只負責自己的內容,除了導致程式碼的重複之外沒有任何壞處。好處是如果專案嚴格執行單一職責方式,當我們改動任何程式碼的時候,不會擔心對全域性造成的影響。那麼重複的程式碼怎麼解決,這個其實webpack的部分和gzip的部分已經能把我們的程式碼進行優化了。

延伸思考:如果壓縮打包的角度可以去掉重複程式碼的部分,那麼更多角度考慮程式碼的可持續、可維護就好了。

單一樣式來源

簡單來講,就是你的樣式程式碼應該來源於儘可能少的元件,最好是維護在一個class中,保證整個樣式程式碼是可追溯的,也可以預想效果的。

案例

<div class="blog">
  <div class="blog-header"></div>
</div>
<div class="calendar">
  <div class="calendar-header"></div>
</div>
複製程式碼
/* calendar css*/
.calendar-header{
}
/* blog css*/
.blog-header{
}
.blog .blog-header{}
複製程式碼

以blog進行title的限定,主要是限制讓字號小一點,這種的主要問題是當專案積累下來,會有很多樣式程式碼散落在各個元件,這樣導致不可追溯。(補充個常識,以父元素為什麼進行修飾子元素,這個稱為樣式上下文)

那麼建議的方式是將散落在各處的程式碼完全控制在一個元件內。

元件修飾符

雖然單一元件的原則讓我們對元件的樣式程式碼封裝的很好,但還是有一些特殊的需求,那我們如何設計這方面的程式碼呢?通過皮膚或者子模組來實現,也可以稱為元件修飾符。

比如我們針對日曆的元件,我們需要追加一個特殊的樣式,可以這樣實現。

.calendar-header{
}
.calendar--nested .calendar-header{
}
複製程式碼

通過這樣的方式,我們不但可以實現皮膚的需求,也可以實現對上下文的解耦,我們只要關注元件需要什麼樣的特殊樣式,進行元件修飾即可,而不用依賴耦合在父容器裡。

element-ui的css

以下以element的2.4.11版本為例,根據其api文件以及原始碼的角度為大家分析element-ui是如何解決這些問題的。

el-button分析

  • el-button文件地址

  • api設計規範:可以看到button的暴露的api主要是基於屬性的

    引數 說明 型別 可選值 預設值
    size 尺寸 string medium / small / mini
    type 型別 string primary / success / warning / danger / info / text
    plain 是否樸素按鈕 boolean false
    round 是否圓角按鈕 boolean false
    circle 是否圓形按鈕 boolean false
    loading 是否載入中狀態 boolean false
    disabled 是否禁用狀態 boolean false
    icon 圖示類名 string
    autofocus 是否預設聚焦 boolean false
    native-type 原生 type 屬性 string button / submit / reset button
  • class的設計,雖然元件的設計是基於屬性的,但實際不同的屬性最終都是表現為class的。那麼基於這樣的角度去設計樣式是element-ui的首創或者是vue元件的創新麼?其實早在bootstrap裡就是這樣設計的。

    class 說明
    el-button-group 按鈕組的樣式,作為外部容器,不對el-button產生任何樣式程式碼,但對其內含有的el-button會產生垂直居中的效果,還有向右的間距,.el-button-group .el-button--primary:first-child{}
    el-button el-button--default 基本樣式,修飾符,生效規則el-button--default也是直接定義其樣式規則,不依賴於el-button
    el-button el-button--medium el-button--medium 修飾符中直接定義好尺寸的全部程式碼
    el-button el-button--default is-circle 基本樣式,smacss狀態樣式 ,其中is-circle包含這個原型的全部樣式,其生效的條件是與el-button同時生效,is-disabled同理
    icon="el-icon-edit" 帶icon的部分不在button裡做特殊樣式,其樣式屬於基本樣式,但是dom結構是通過元件進行新增的,那麼其圖示的字號來源於哪裡呢?來源於el-button的基本樣式14px,所以這部分進行了解耦,不用單獨設定
  • 元件內如何根據屬性進行返回對應的class.

class="el-button"
// 可以看到其根據傳入的各個type屬性分別將class追加到class陣列之中
:class="[type ? 'el-button--' + type : '',buttonSize ? 'el-button--' + buttonSize : '',{'is-disabled': buttonDisabled,'is-loading': loading,'is-plain': plain,'is-round': round,'is-circle': circle}]"
//根據為loading 顯示出固定的loading圖示
<i class="el-icon-loading" v-if="loading"></i>
// 根據傳入的icon以及沒有loading顯示出對應type的icon
<i :class="icon" v-if="icon && !loading"></i>
複製程式碼

輕度定製的element

經常有場景我們需要引入element-ui之後需要對其ui進行皮膚化的開發樣式,雖然element-ui有暴露其對應的元件樣式,我們也可以進行對應的詳細的原始碼的fork並開發,但在大多數中小公司其實不用小題大做。我們只需要根據自己的情況,針對一樣的樣式進行全域性樣式覆蓋即可。那麼,我們專案中針對按鈕的皮膚化改變會是這樣的。

// 定義variables.scss的主題變數
// 主題色相關變數
$color-thin:rgba(60,191,196,0.1);
$color:rgba(60,191,196,1);

// customer-element.scss  主要定義已使用餓了麼元件樣式的修改
@import  './variables';
// 一個按鈕樣式的全域性覆蓋
.el-button--primary{
&:hover,&:focus{
background:$color;
border-color:$color;
}
background:$color;
border-color:$color;
}
複製程式碼

bootstrap的css

我們同樣也是分析bootstrap裡的樣式使用,基本也是使用class拼盤理論的。

<div class="btn-group" role="group" aria-label="...">
  <button type="button" class="btn btn-default">Left</button>
  <button type="button" class="btn btn-default">Middle</button>
  <button type="button" class="btn btn-default">Right</button>
</div>
複製程式碼

button.less樣式檔案節選,可以看到其基本樣式、巢狀內偽類的樣式,button-size的方法類,修飾符的(子模組的)維護方式,這都是值得我們借鑑的科學規範思想。


// Base styles
// --------------------------------------------------

.btn {
  display: inline-block;
......
  .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);
  .user-select(none);
  &,
  &:active,
  &.active {
    &:focus,
    &.focus {
      .tab-focus();
    }
  }

  &:hover,
  &:focus,
  &.focus {
    color: @btn-default-color;
    text-decoration: none;
  }

  a& {
    &.disabled,
    fieldset[disabled] & {
      pointer-events: none; // Future-proof disabling of clicks on `<a>` elements
    }
  }
}

// Alternate buttons
// --------------------------------------------------
.btn-default {
  .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
}
.btn-primary {
  .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
}

複製程式碼

小結

到此為止總結的技巧點如下:當然肯定還有更多的技巧等待你補充

  • 分離容器與內容
  • 單一樣式來源
  • 區分佈局與元件的角色
  • 在標記上使用單一的、扁平的標識
  • 元件修飾符

通過本文我們瞭解並總結了css程式碼的繼承與特殊化的問題,並通過上面的技巧解決了這樣的痛點,並用來顯著提升自己的程式碼水平。

相關文章