Sass(Scss) Memo: @extend

今回は@extendについてです。
別のセレクタの全てのスタイルを継承することができます。

Scss
.borderRed {
    border: 1px solid red;
}
.selectorA {
    @extend .borderRed; // ここで.borderRedを継承指定
    padding: 2px;
}
出力結果のCSS
.borderRed, .selectorA { /* ここに .selectorA が追加されます */
  border: 1px solid red;
}

.selectorA {
  padding: 2px;
}

@extendに使用できるセレクタ

1つの要素のみに関連するセレクタであればほとんど使用できます。 要素セレクタやid/classセレクタや擬似クラス、属性セレクタなども使用できますし、各セレクタを連結させたもの(div#selectorA.selectorBdiv.selectorA:first-childなど)も使用できます。

ただ、いまのところ(Sassバージョン3.1.1)は子孫/子/兄弟セレクタ(E FE > FE ~ F)と隣接セレクタ(E + F)は使用できません。
全称セレクタ(*)も使用できますが、使う場面はなさそうですね。

あと、@extend .selectorA, .selectorB;みたいに複数一括指定することはできないので分けて書きましょう。

Scss
.selectorA {
    @extend div;
    @extend #id;
    @extend .class;
    @extend div.class; // 要素も指定しているので、p.classがあっても継承しません
    @extend #id.class;
    @extend .class-1.class-2;
    @extend .selector:hover;
    @extend .selector[attr];
    @extend *; // これは使うことなさそうだけど...

    // ここから下はエラーになります
    @extend div .class; // E F
    @extend div > .class; // E > F
    @extend div + .class; // E + F
    @extend div ~ .class; // E ~ F
}

@extendをうまく使えば、セレクタのグループ化によってCSSのファイルサイズを減量したり、スプライト用の画像の読み込み指定をまとめてhttpリクエストを減らすこともできるのでとても便利です。

が、、気をつけなければいけないことがあります。

気をつけること 1

継承するのは@extendで指定したセレクタ自身のスタイルだけではなく、その子要素などのスタイルも継承するということです。

Scss
.selectorA {
    padding: 20px;
    border: 1px solid #333;
    //
    h2 {
        font-weight: bold;
        font-size: 20px;
    }
    p {
        color: #666;
    }
}
body.firefox .selectorA {
    padding: 10px;
}
.selectorB {
    @extend .selectorA; // ここで.selectorAを継承指定
    background: #eee;
    //
    h2 {
        font-size: 14px;
    }
}
CSS
.selectorA, .selectorB {
  padding: 20px;
  border: 1px solid #333;
}
.selectorA h2, .selectorB h2 { /* ここにも追加されました */
  font-size: 20px;
}
.selectorA p, .selectorB p { /* ここにも追加されました */
  color: #666;
}

body.firefox .selectorA, body.firefox .selectorB { /* ここにも追加されました */
  color: #666;
}

.selectorB {
  background: #eee;
}

.selectorB h2 {
  font-size: 14px;
}

こういう問題があるかもしれません。

  • .selectorA h2は太字にしたいけど、.selectorB h2はスタイルリセット時にfont-weight: normal;にしてるからそのままでよかったのに...。
  • .selectorB pのテキストカラーはbodyで適用されてる色でよかったのに...。
  • body.firefox .selectorBはスタイル変える必要がないのに...。
  • 後日、.selectorA h2のテキストカラーを変更することになったのでスタイル追加したら、当然 .selectorB h2にも適用された...。
    複数人で制作していると、別のメンバーがつくったセレクタを継承してたらいつの間にか結構変わってたとかセレクタ自体が削除されてた(エラー出ないので気づかない)なんてこともあるかも。

気をつけること 2

@extendで指定したセレクタを含むセレクタも継承することです。

たとえば下記のような場合、要素指定なしの.selectorAのスタイルだけを継承したいとします。

Scss
.selectorA {
    text-decoration: none;
}
a.selectorA {
    text-decoration: underline;
}
.selectorB {
    @extend .selectorA; // ここで.selectorAを継承指定
}

ところが、期待通りの出力結果になりませんでした。

CSS
.selectorA, .selectorB {
  text-decoration: none;
}

a.selectorA, a.selectorB { /* こちらにも追加されている */
  text-decoration: underline;
}

さらに属性セレクタに関してはバグ(?)があるようです。

.selectorBで属性セレクタを含んでいる.selectorA@extendします。

Scss
a.selectorA {
    text-decoration: none;
    //
    &[href] {
        text-decoration: underline;
    }
}
.selectorB {
    @extend .selectorA;
}

期待する出力結果は下記になります。

CSS
a.selectorA, a.selectorB {
  text-decoration: none;
}

a.selectorA[href], a.selectorB[href] {
  text-decoration: underline;
}

実際の出力結果では、意図しないセレクタが出力されます。

CSS
a.selectorA, a.selectorB {
  text-decoration: none;
}

a.selectorA[href], a[href].selectorB { /* これはどういうことですか... */
  text-decoration: underline;
}

複雑なセレクタに使うとややこしいですね。。
@extendを使用するルールは決めておいたほうが良いと思います。

@extendの使用ルール案
  • @extendで使用するセレクタはスタイルや要素が、ある程度固定されたものにする。
    おなじみのclearfixやスプライト用の指定とか。
  • @extend専用のセレクタ(.extend-fooとか)を用意して利用する。
    このセレクタに対するあらゆる変更は、それを利用している他のセレクタにも影響することが分かるので、変更は注意しておこなうだろうし、変更前に影響範囲を確認するというフローをつくることもできると思います。