Sass の少し突っ込んだ使い方について、かなりの方が見てくださったようなので今回もちょっと変わった内容を紹介したいと思います。いまいち使い方の分からない @contetn について今回はスポットを当ててみたいと思います。
@contentの動作について
@mixin mixinTest() {
div.testclass {
@content;
}
}
このように @content を使用すると、以下のような @mixin の書き方ができます。
@include mixinTest() {
width: 500px;
}
結果、出力されるのは以下のCSSのコードです。
div.testclass {
width: 500px; }
このように、 @include 関数名() {…} といった書き方ができるようになります。これは @content を @mixin 内に記述することでこのような書き方ができるのです。言い方を変えますと、 @content を使用していない @mixin に対して @include 関数名() {…} といった記述をするとコンパイルエラーとなります。
@contentで出来ないこと
以下のコードは実験時に作成したソースです。
@mixin mixinTest() {
div.testclass {
@content;
$value: 500px !default;
width: $value;
}
}
@include mixinTest() {
$value: 1000px;
}
@content で内容が単に展開されるのであれば !default は打ち消され上書きされそうな気がします。しかし、上書きはされません。同様に以下のソースはコンパイルエラーとなります。
@mixin mixinTest() {
div.testclass {
@content;
// そのまま展開されるならば変数は定義されているはず...
// $value: 500px !default;
width: $value;
}
}
@include mixinTest() {
$value: 1000px;
}
どうやら、ただ展開しているわけではなさそうです。いろいろ遊べそうな気がしましたが、あまり自由度はないようですね。
@contentと他機能との比較
上記の説明だけだと、正直に申しますと引数との違いがよくわかりません。実際に以下のように記述すれば同様の汎用性は得られます。むしろ、 @content を使用しない方が拘束力があるためライブラリ使用者にとっては安全に使用できます。
@mixin mixinTest($value) {
div.testclass {
width: #{$value};
}
}
@include mixinTest(500px);
@content によるマークアップの構造化に対しても少々疑問があります。以下のソースを見てください。
@mixin createContent() {
div.content {
@content;
}
}
@include createContent() {
div.posts {...}
div.menu {...}
}
上記のように記述し、よく使うマークアップ用のクラスをまとめておけばマークアップ自体をテンプレート化できます。しかし、これもまた以下と同様の汎用性も持ったコードです。
@mixin createPosts() {
div.posts {...}
}
@mixin createContent() {
div.content {
@include createPosts();
// 以下 createMenu()やcreateなんたら()など
}
}
また、以下の2つのソースも同様です。
$margin-top: null;
$margin-left: null;
@mixin setSize($type) {
@if ($type == absolute) {
@content;
position: $type;
top: $margin-top;
left: $margin-left;
$margin-top: null;
$margin-left: null;
}
@else if ($type == relative) {
@content;
position: $type;
top: $margin-top;
left: $margin-left;
$margin-top: null;
$margin-left: null;
}
@else if ($type == fixed) {
@content;
position: $type;
top: $margin-top;
left: $margin-left;
$margin-top: null;
$margin-left: null;
}
@else if ($type == static) {
@content;
position: $type;
margin-top: $margin-top;
margin-left: $margin-left;
$margin-top: null;
$margin-left: null;
}
}
body {
@include setSize(absolute) {
$margin-top: 300px;
$margin-left: 100px;
}
}
@mixin setSize($type: null, $top: null, $left: null) {
@if ($type == absolute) {
@content;
position: $type;
top: $top;
left: $left;
}
@else if ($type == relative) {
@content;
position: $type;
top: $top;
left: $left;
}
@else if ($type == fixed) {
@content;
position: $type;
top: $top;
left: $left;
}
@else if ($type == static) {
@content;
position: $type;
margin-top: $top;
margin-left: $left;
}
}
body {
@include setSize(absolute, 300px, 100px);
}
結局、 @mixin の定義内で @mixin を呼び出すことで同じことができるようになります。同じことができると断言できてしまうのは @content 呼び出し先と、呼び出し元のスコープが異なっているためです。これは前述しましたが、単に展開しているわけではないためです。こうなると、値の受け渡しはグローバル変数か引数に頼らざるおえません。では、 @content による処理自体の切り替えはどうなのかというと、 @if で @content の呼び出し方を変える以上、 @mixin を通常通り呼び出すことと何ら変わりがないです。違いは、 @content を含む @mixin を呼び出す末端のコーディングで処理を記述するか、予め用意した createPosts() などにコーディングするかの違いでしかありません。
@content の場合、 CSS の記述自体をユーザに委ねることになるため不具合の原因が特定しづらくなります。自作のライブラリ側に問題があるのか、呼び出し方に問題があるのかをまず特定しなければならないためです。個人的には、 @content に何がしかのメリットがなければ使用しません。
@contentを使う場面とは
これだけ否定しましたが、メリットは確かにあります。それは「可読性が上がる」ことです。 @content は性質上、 @mixin の呼び出し時にコードが記述できるため引数やグローバル変数の設定時に「{…}」が使えます。以下のコードを見て下さい。
$GLOBAL_VALUE1: null !default;
$GLOBAL_VALUE2: null !default;
@mixin setGlobalParam($type) {
@if ($type == A) {
@content;
$GLOBAL_VALUE2: $GLOBAL_VALUE1 * 2;
}
@else if ($type == B) {
@content;
$GLOBAL_VALUE2: $GLOBAL_VALUE1 * 4;
}
}
@include setGlobalParam(A) {
$GLOBAL_VALUE1: 1000px;
}
実践的なソースではなくて申し訳ありませんが、引数の設定と、グローバル変数の設定を明確に区別することがお分かりになるかと思います。通常通り全て引数でも全く同様の処理ができますが、処理内容を区別できるということは良いことではないかと思います。
以上が @content の実験とその結果です。現状ではまだ @content を使用するメリットよりもデメリットの方が大きいようです。しかし、私の知らないテクニックが存在しているだけかもしれません。その時は、 Twitter にて連絡頂ければ嬉しいです。