HTML5Experts.jp

Sass 3.3で追加された新しいデータタイプ「マップ」まとめ解説

前回の記事では、Sass 3.3で追加される「&」の新機能と@at-rootについて解説しました。今回は新しいデータタイプの「マップ」について解説します。

マップは色々な使い道があると思いますし、使い方によってはかなり便利なものですので、ライブラリを作っている方などは特に覚えておくと良いと思います。

マップとは

マップは任意の名前と値のペアが集まったもので、名前をキーにして値を設定したり、取り出して使います。

マップの書き方ですが、名前と値をコロン(:)で区切り、複数記述する場合はカンマ(,)で区切り、それらを丸括弧(())で囲みます。CSSのスタイルの書き方とちょっと似ていますね。

// マップ
$map: (
    key1: value1,  // key1にvalue1を設定
    key2: value2,
    key3: value3,
);

// CSSのスタイル selector { property1: value1; // property1にvalue1を設定 property2: value2; property3: value3; }

マップの値には、マップも含めたすべてのタイプのデータを書くことができます。

$map: (
    key1: 10px,             // 数値
    key2: "string",         // 文字列
    key3: #fff,             // カラー
    key4: true,             // ブーリアン
    key5: null,             // null
    key6: (foo, bar, baz),  // リスト(カンマ区切り)
    key7: foo bar baz,      // リスト(スペース区切り)
    key8: (                 // ネストしたマップ
        key1: value1,
        key2: value2,
        ...
    ),
);

Sass 3.3よりも前までは、複雑なデータ構造を作るにはリストを使って頑張るしかありませんでした。 ですが、マップを使えば、より分かりやすく書くことができますし、専用の関数もあるのでデータを扱いやすくなります。

マップ用の関数

マップ用の関数は5つあります。

map-get()

map-get()は、指定したキーの値を取得する関数です。

// 引数の1つ目にマップ、2つ目にキーを指定
map-get($map, $key)

次のようにして使います。

$map: (
    key1: 10px,
    key2: 20px,
    key3: (
        nested-key1: red,
        nested-key2: blue,
    ),
);

.map { margin: map-get($map, key1); }

これをコンパイルすると次のようになります。

.map {
  margin: 10px;
}

指定したキーがマップにない場合は、nullが返されます。

また、map-get()を複数回使うことで、ネストされたマップから値を取得することができます。

.map {
    color: map-get( map-get($map, key3), nested-key1 );
}

これをコンパイルすると次のようになります。

.map {
  color: red;
}

何回もmap-get()を書くのは正直面倒くさいですし、コードが読みづらいですね。。

map-merge()

map-merge()は、2つのマップをマージする関数です。

// 引数の1つ目と2つ目にマージしたいマップを指定
map-merge($map1, $map2)

基本的には1つ目のマップの後に、2つ目のマップが追加されますが、同じキーがある場合は2つ目のマップの値で上書きされます。

$map1: (
    key1: value1,
    key2: value2,
    key3: value3,
);

$map2: ( key4: value400, key5: value500, key1: value100, );

$new-map: map-merge($map1, $map2);

この場合、$new-mapの値は次のようになっています。

(key1: value100, key2: value2, key3: value3, key4: value400, key5: value500)

key1は両方のマップにあるので、2つ目のマップの$map2の値が使われています。 そして、key3の後に$map2にあったkey4とkey5が追加されています。

このような特徴を利用して、ライブラリ内の設定用マップなどを自身のプロジェクトに合わせて上書きするといったこともできます。

map-keys()

map-keys()は、1つのマップ内のすべてのキーをカンマ区切りのリストで返す関数です。

// 引数にはマップを指定
map-keys($map)

それではサンプルを見てみましょう。

$map: (
    key1: 10px,
    key2: 20px,
    key3: (
        nested-key1: red,
        nested-key2: blue,
    ),
);

.map { content: map-keys($map); }

これをコンパイルすると次のようになります。

.map {
  content: key1, key2, key3;
}

ネストされたマップ内のキーを取得するには、map-get()を併用する必要があります。

.map {
    content: map-keys( map-get($map, key3) );
}

これをコンパイルすると次のようになります。

.map {
  content: nested-key1, nested-key2;
}

map-values()

map-values()は、1つのマップ内のすべての値をカンマ区切りのリストで返す関数です。

// 引数にはマップを指定
map-values($map)

使い方はmap-keys()と同じです。

.map {
    content: map-values($map);
        //-> 10px, 20px, ( nested-key1: red, nested-key2: blue )
        // 値にマップが含まれるため、実際にはエラーが出ます
}

map-has-key()

map-has-key()は、1つのマップ内に特定のキーがあるかどうか調べる関数です。

// 引数の1つ目にマップ、2つ目に調べたいキーを指定
map-has-key($map, $key)

戻り値はtrueかfalseです。

$map: (
    key1: 10px,
    ...
);

$has-key1: map-has-key($map, key1);

この場合、$has-key1の値は true になっています。

リスト用の関数

マップにはリスト用の関数も使うことができます。

使い方とコンパイル結果を簡単にまとめると次のようになります。

$map1: (
    key1: value1,
    key2: value2,
);

$map2: ( key4: value400, key1: value100, );

.sample { /* nth() */ content: nth($map1, 1); //-> key1 value1

/* length() */
content: length($map1); //-> 2

/* zip() */
content: zip($map1);
    //-> key1 value1, key2 value2
content: zip($map1, $map2);
    //-> key1 value1 key4 value400, key2 value2 key1 value100

/* join() */
content: join($map1, $map2);
    //-> key1 value1, key2 value2, key4 value400, key1 value100
content: join($map1, $map2, space);
    //-> key1 value1 key2 value2 key4 value400 key1 value100

/* append() */
content: append($map1, foo bar);
    //-> key1 value1, key2 value2, foo bar
content: append($map1, foo bar, space);
    //-> key1 value1 key2 value2 foo bar

/* index() */
content: index($map1, key1); //-> false
content: index($map1, key1 value1); //-> 1

}

@eachで使う

@eachはSass 3.3から複数の変数を指定できるようになったので、マップのキーと値を別々の変数に入れて利用することができます。

.box {
    $config: (
        warn: red,
        info: blue,
    );

// キーは$classに、値は$bg-colorに入ります
@each $class, $bg-color in $config {
    @at-root #{&}-#{$class} {
        background-color: $bg-color;
    }
}

}

これをコンパイルすると次のようになります。

.box-warn {
  background-color: red;
}
.box-info {
  background-color: blue;
}

これまでと比べて、断然扱いやすいものになっていると思います。

可変長キーワード引数

マップを可変長引数のような感じでミックスインや関数に渡すことができます。 可変長引数と可変長キーワード引数のそれぞれの場合を、次のミックスインを使って解説します。

@mixin box($border, $bgColor, $color) {
    border: $border;
    background-color: $bgColor;
    color: $color;
}

まずは可変長引数を使った場合です。

.box {
    // 値はミックスインの引数の順番に合わせる
    $config: 1px solid #ccc, #fff, #333;

// 変数の後ろに「...」をつけ、可変長引数としてミックスインに渡す
@include box($config...);

}

$configの1つ目の「1px solid #ccc」が$borderに、2つ目の「#fff」が$bgColorに、そして3つ目の「#333」が$colorに渡されます。 これをコンパイルすると次のようになります。

.box {
  border: 1px solid #cccccc;
  background-color: white;
  color: #333333;
}

次にマップを使った可変長キーワード引数の場合です。

.box {
    $config: (
        // キーはミックスインの引数名に合わせる。記述順は問わない
        color: #333,
        bgColor: #fff,
        border: 1px solid #ccc,
    );

@include box($config...);

}

コンパイル結果は可変長引数の場合と同じになります。

マップを使った場合は、キーと同名のミックスインの引数に値が渡されます。キー「color」はミックスインの引数の「$color」に対応するということです。 そのため、マップ内のキーをミックスインの引数の順番に合わせて記述する必要はありません。

ちなみに、可変長引数と併用することもできますが、その場合は可変長キーワード引数を可変長引数の後に記述する必要があります。

また、Sass 3.3から追加されたkeywords()関数を使って、可変長キーワード引数をマップに変換する関数をつくることができるようです。

@function create-map($args...) {
  @return keywords($args);
}

$map: create-map($key1: 10px, $key2: 20px);

この場合、$mapの値は (key1: 10px, key2: 20px) になっています。

マップのような構造のリスト

リストで2つの値をペアにして、マップのようにして使っていた場合、そのリストに対してマップ用の関数を使うことができます。

$map: (
    key1 value1,
    key2 value2,
);
$map2: (
    key4 value400,
    key1 value100,
);

@debug map-get($map, key1); //-> value1 @debug map-merge($map, $map2); //-> (key1: value100, key2: value2, key4: value400) @debug map-keys($map); //-> key1, key2, key3 @debug map-values($map); //-> value1, value2, value3 @debug map-has-key($map, key2); //-> true

ただし、将来的にはこのようなリストに対してマップ用の関数を使うことはできなくなるようで、コンパイル時には警告が出されます。Sass 3.3が使えるようになったら、マップに切り替えていった方がが良さそうです。

おわりに

新しいデータタイプ「マップ」を解説しました。筆者としては、自前のライブラリ内のリストでなんとかしている箇所をとりあえず置き換えるつもりですが、みなさんはどのように使われますか?