點燈坊

失くすものさえない今が強くなるチャンスよ

如何重構 CSS 重複 Property ?

Sam Xiao's Avatar 2020-07-16

CSS 在實務上常會遇到重複 Property,為了方便日後維護,應盡量避免重複,本文介紹兩種重構方式,各有其優缺點。

Version

macOS Catalina 10.15.5
WebStorm 2020.1.3
Vue 2.6.11
CSS 3

Duplicate CSS

refactor000

上方顯示 book,下方顯示 category,雖然資料並不同,但顯示結果類似,也就是其 CSS style 相似,重複的 property 甚多。

<template>
  <div>
    <div class="book-list">
      <a href="#" class="book-item">FP in JavaScript</a>
      <a href="#" class="book-item">RxJS in Action</a>
      <a href="#" class="book-item">Speaking JavaScript</a>
    </div>

    <div class="category-list">
      <a href="#" class="category-item">FP</a>
      <a href="#" class="category-item">FRP</a>
      <a href="#" class="category-item">JavaScript</a>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped>
.book-list {
  border: 1px solid #ccc;
  padding: 20px;
  margin-top: 10px;
}

.book-item {
  color: red;
  text-decoration: none;
  display: list-item;
}

.category-list {
  border: 1px solid #ccc;
  padding: 20px;
  margin-top: 20px;
}

.category-item {
  color: blue;
  text-decoration: none;
  display: list-item;
}
</style>

第 3 行

<div class="book-list">
  <a href="#" class="book-item">FP in JavaScript</a>
  <a href="#" class="book-item">RxJS in Action</a>
  <a href="#" class="book-item">Speaking JavaScript</a>
</div>

上方顯示 book,<div> 使用 book-list class,而 <a> 使用 book-item class。

第 9 行

<div class="category-list">
  <a href="#" class="category-item">FP</a>
  <a href="#" class="category-item">FRP</a>
  <a href="#" class="category-item">JavaScript</a>
</div>

下方顯示 category,<div> 使用 category-list class,而 <a> 使用 category-item class。

24 行

.book-list {
  border: 1px solid #ccc;
  padding: 20px;
  margin-top: 10px;
}

35 行

.category-list {
  border: 1px solid #ccc;
  padding: 20px;
  margin-top: 10px;
}

.category-list 除了 margin-top 不同外,其餘與 .category-list 完全一樣。

30 行

.book-item {
  color: red;
  text-decoration: none;
  display: list-item;
}

42 行

.category-item {
  color: blue;
  text-decoration: none;
  display: list-item;
}

.book-item.category-item 也類似,僅有 color 不同。

Class Selector

<template>
  <div>
    <div class="list book-list">
      <a href="#" class="item">FP in JavaScript</a>
      <a href="#" class="item">RxJS in Action</a>
      <a href="#" class="item">Speaking JavaScript</a>
    </div>

    <div class="list category-list">
      <a href="#" class="item">FP</a>
      <a href="#" class="item">FRP</a>
      <a href="#" class="item">JavaScript</a>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped>
.list {
  border: 1px solid #ccc;
  padding: 20px;
}

.list.book-list {
  margin-top: 10px;
}

.list.category-list {
  margin-top: 20px;
}

.item {
  text-decoration: none;
  display: list-item;
}

.book-list .item {
  color: red;
}

.category-list .item {
  color: blue;
}
</style>

24 行

.list {
  border: 1px solid #ccc;
  padding: 20px;
}

.book-list.category-list 共同的 property 抽成 .list

37 行

.item {
  text-decoration: none;
  display: list-item;
}

.book-item.category-item 共同的 property 抽成 .item

第 3 行

<div class="list book-list">
  <a href="#" class="item">FP in JavaScript</a>
  <a href="#" class="item">RxJS in Action</a>
  <a href="#" class="item">Speaking JavaScript</a>
</div>

在 HTML 一樣使用 .book-list,但沒使用 .book-item

這裡的 .book-list 只是為了 class selector 辨識用。

29 行

.list.book-list {
  margin-top: 10px;
}

.list.category-list {
  margin-top: 20px;
}

以 multiple class selector 辨識出是 book list 或 category list,然後補上 CSS 特殊部分。

42 行

.book-list .item {
  color: red;
}

.category-list .item {
  color: blue;
}

以 descendant combinator 辨識出 book item 或 category item,然後補上 CSS 特殊部分。

Extract Class

<template>
  <div>
    <div class="list book-list">
      <a href="#" class="item book-item">FP in JavaScript</a>
      <a href="#" class="item book-item">RxJS in Action</a>
      <a href="#" class="item book-item">Speaking JavaScript</a>
    </div>

    <div class="list category-list">
      <a href="#" class="item category-item">FP</a>
      <a href="#" class="item category-item">FRP</a>
      <a href="#" class="item category-item">JavaScript</a>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped>
.list {
  border: 1px solid #ccc;
  padding: 20px;
}

.book-list {
  margin-top: 10px;
}

.category-list {
  margin-top: 20px;
}

.item {
  text-decoration: none;
  display: list-item;
}

.book-item {
  color: red;
}

.category-item {
  color: blue;
}
</style>

24 行

.list {
  border: 1px solid #ccc;
  padding: 20px;
}

一樣將 .book-list.category-list 共同的 property 抽成 .list

29 行

.book-list {
  margin-top: 10px;
}

.category-list {
  margin-top: 20px;
}

.book-list.category-list 則只留下相異部分。

37 行

.item {
  text-decoration: none;
  display: list-item;
}

一樣將 .book-item.category-item 共同的 property 抽成 .item

42 行

.book-item {
  color: red;
}

.category-item {
  color: blue;
}

.book-item.category-item 則只留下相異部分。

第 3 行

<div class="list book-list">

由於原本 .book-list 拆成 .list.book-list,因此要改成組合 .list.book-list

第 4 行

<a href="#" class="item book-item">FP in JavaScript</a>

由於原本 .book-item 拆成 .item.book-item,因此要改成組合 .item.book-item

category-listcategory-item 也同理。

Conclusion

  • 第一種寫法充分發揮 class selector 特性,使用了 multiple class selector 與 descendant combinator,class 只是用來給 selector 辨識使用,優點是 class 較少,缺點是必須很熟悉 class selector 各種 syntax
  • 第二種寫法充滿 FP 思維,抽出共用 class,並實作出特殊 class,並沒有使用任何特殊 syntax,最後在 HTML 組合使用,優點是非常靈活,且只用了最基礎 class selector,缺點是 class 較多
  • 實務上常看到第一種寫法,這算是 CSS 傳統風格,在 CSS 透過靈活使用 class selector 完成目標;第二種則屬於 Function CSS 風格,只使用最基本 CSS,由 HTML 組合 class 完成目標

Reference

李建杭, 金魚都能懂的 CSS 選取器:金魚都能懂了你還怕學不會嗎