Skip to content

Sass sass:map:取值、合併與巢狀路徑

Sass 對照表模組 sass:map 的函式

map 是 Sass 組織設定的核心資料結構,sass:map 提供操作它的函式。

scss
@use "sass:map";

取值

scss
$theme: (primary: #2965f1, danger: #e03131);

map.get($theme, primary)   // #2965f1
map.get($theme, nope)      // null

巢狀時可以一次鑽好幾層:

scss
$config: (
  button: (
    primary: (bg: #2965f1, text: #fff),
  ),
);

map.get($config, button, primary, bg)   // #2965f1

中途任一層取不到就回傳 null,不會報錯。

檢查

scss
map.has-key($theme, primary)              // true
map.has-key($config, button, primary)     // true,也支援多層
map.keys($theme)                          // primary, danger
map.values($theme)                        // #2965f1, #e03131

安全取值:這段值得抄進每個專案

scss
@use "sass:map";

@function get($map, $keys...) {
  @if not map.has-key($map, $keys...) {
    @error "找不到路徑 #{$keys},第一層可用的鍵有:#{map.keys($map)}。";
  }
  @return map.get($map, $keys...);
}
scss
.btn { background: get($config, button, primary, bg); }
.err { background: get($config, button, primry, bg); }  // 編譯期就報錯

沒有這一層的話,打錯字只會讓那行宣告安靜消失。錯誤訊息寫法見 除錯

新增與移除

scss
map.set($theme, info, #1c7ed6)      // 回傳新 map
map.set($config, button, primary, bg, #000)  // 也能設巢狀路徑
map.remove($theme, danger)

這些函式都不會改到原本的 map

scss
map.set($theme, info, #1c7ed6);        // ❌ 結果被丟掉
$theme: map.set($theme, info, #1c7ed6); // ✅

merge:只合併第一層

scss
$a: (primary: #2965f1, danger: #e03131);
$b: (danger: #c92a2a, info: #1c7ed6);

map.merge($a, $b)
// (primary: #2965f1, danger: #c92a2a, info: #1c7ed6)

第二個 map 的值蓋掉第一個的同名鍵。

但遇到巢狀就會出事:

scss
$base: (btn: (bg: #2965f1, radius: 4px, height: 40px));
$over: (btn: (bg: #e03131));

map.merge($base, $over)
// (btn: (bg: #e03131))   ← radius 與 height 都不見了

merge 把整個 btn 換掉了。

deep-merge:逐層合併

scss
map.deep-merge($base, $over)
// (btn: (bg: #e03131, radius: 4px, height: 40px))  ✅

做主題覆寫、元件設定合併時幾乎都該用這個。

scss
$default-theme: (
  color: (bg: #fff, text: #333, border: #ddd),
  space: (sm: 8px, md: 16px),
);

$user-theme: (
  color: (bg: #1a1a1a, text: #f5f5f5),
);

$theme: map.deep-merge($default-theme, $user-theme);
// border 與 space 都保留下來了

deep-remove

scss
map.deep-remove($config, button, primary, bg)

移除巢狀路徑上的某一項。

設計代幣的組織方式

實務上的典型結構:

scss
// abstracts/_tokens.scss
@use "sass:map";

$tokens: (
  color: (
    brand:   (50: #e7f0ff, 500: #2965f1, 900: #16357f),
    neutral: (50: #f8f9fa, 500: #868e96, 900: #212529),
  ),
  space: (1: 4px, 2: 8px, 3: 16px, 4: 24px, 5: 32px),
  radius: (sm: 4px, md: 8px, full: 999px),
) !default;

@function token($keys...) {
  @if not map.has-key($tokens, $keys...) {
    @error "找不到代幣 #{$keys}。";
  }
  @return map.get($tokens, $keys...);
}
scss
@use "../abstracts" as a;

.card {
  padding: a.token(space, 3);
  border-radius: a.token(radius, md);
  background: a.token(color, neutral, 50);
}

加上 !default 讓外部能用 @use ... with 整組換掉,架構見 abstracts 該放什麼

常見問題

merge 和 deep-merge 差在哪?

merge 只合併第一層,第二層的值會被整個換掉;deep-merge 則會逐層往下合併。做主題覆寫時通常要用 deep-merge,否則只想改一個顏色卻會弄丟同一層的其他設定。

怎麼取巢狀 map 裡面的值?

map.get() 可以一次接多個鍵,依序往下鑽。中途任何一層取不到就回傳 null,不會報錯,所以路徑長的時候更需要包一層檢查。

為什麼 map.set 之後 map 沒有變?

因為 map 是不可變的,setmerge 都是回傳新的 map。要保留結果必須把回傳值指派回原本的變數。

怎麼避免打錯鍵名造成樣式消失?

包一層取值函式,先用 map.has-key() 檢查,不存在就用 @error 中斷並列出可用的鍵。這樣打錯字會在編譯期就被抓到,而不是等到畫面壞掉才發現。

延伸閱讀