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 是不可變的,set 與 merge 都是回傳新的 map。要保留結果必須把回傳值指派回原本的變數。
怎麼避免打錯鍵名造成樣式消失?
包一層取值函式,先用 map.has-key() 檢查,不存在就用 @error 中斷並列出可用的鍵。這樣打錯字會在編譯期就被抓到,而不是等到畫面壞掉才發現。