SCSS嵌套变量调用与自动识别嵌套层级的实现咨询
首先,先解决你最开始的疑问:SCSS完全支持在变量中嵌套多个变量(也就是嵌套Map),map-get其实是可以处理深层嵌套的,只是你需要用正确的调用方式。比如你定义的$themes,要获取light主题下的text-first颜色,你可以这样写:
// 多层嵌套调用map-get map-get(map-get(map-get($themes, light), text), first); // 如果你的Sass版本在3.3及以上,还可以用简化的多参数写法 map-get($themes, light, text, first);
接下来看你升级后的需求:希望通过theme函数和mixin实现自动化主题样式,同时兼容一级和二级嵌套的key。你的现有代码问题在于,$theme-map只存储了一级键值对(比如text对应的是一个子Map,而不是直接的颜色值),所以当你调用theme('text-second')时,函数找不到对应的key。
下面提供两种可行的解决方案,你可以根据自己的习惯选择:
方案一:扁平化嵌套Map(推荐,适配你当前的调用方式)
核心思路是在mixin里把所有嵌套的Map结构扁平化,将二级key转换成父key-子key的格式存入$theme-map,这样你的theme('text-second')调用就能直接生效。
步骤1:编写扁平化Map的辅助函数
这个函数会递归遍历嵌套Map,把所有层级的key用-连接,转成扁平的键值对:
@function flatten-map($map, $prefix: '') { $result: (); @each $key, $value in $map { // 生成新的key:如果有前缀就用前缀+分隔符+当前key,否则直接用当前key $new-key: if($prefix == '', $key, #{$prefix}-#{$key}); // 如果当前值还是Map,继续递归扁平化;否则存入结果 @if type-of($value) == 'map' { $result: map-merge($result, flatten-map($value, $new-key)); } @else { $result: map-merge($result, ($new-key: $value)); } } @return $result; }
步骤2:修改你的theme Mixin
把原来的循环替换成调用上面的扁平化函数,直接生成包含所有层级key的$theme-map:
@mixin theme($themes) { @each $theme, $map in $themes { .theme-#{$theme} & { $theme-map: flatten-map($map) !global; @content; // 使用完后清空全局变量 $theme-map: null !global; } } }
步骤3:保持你的theme函数不变
现在$theme-map里已经包含了text-first、text-second、button这些key,你的原有调用完全可以正常工作:
.past { @include theme($themes) { color: theme('text-second'); // 会正确取到grey background-color: theme('button'); // 会正确取到blue } }
方案二:让theme函数支持多级Key查询
如果你希望保留原有的嵌套Map结构,不想扁平化,可以修改theme函数,让它能解析多级key(支持传入多个参数或者带分隔符的字符串)。
步骤1:编写嵌套Map取值的辅助函数
这个函数会递归遍历多级key,从Map中获取对应的值:
@function get-nested-value($map, $keys) { $current-value: $map; @each $key in $keys { $current-value: map-get($current-value, $key); // 如果某个key不存在,抛出警告并返回null @if $current-value == null { @warn "Key '#{$key}' not found in the theme map"; @return null; } } @return $current-value; }
步骤2:修改theme函数,支持两种调用方式
让函数既能处理theme('text', 'first')这种多参数形式,也能处理theme('text-first')这种带分隔符的字符串形式:
@function theme($keys...) { // 先处理字符串分割的情况 @if length($keys) == 1 and type-of(nth($keys, 1)) == 'string' { $key-string: nth($keys, 1); $split-keys: (); $delimiter: '-'; $delimiter-index: str-index($key-string, $delimiter); // 手动实现字符串分割(兼容旧版Sass,新版可以直接用str-split) @while $delimiter-index != null { $split-keys: append($split-keys, str-slice($key-string, 1, $delimiter-index - 1)); $key-string: str-slice($key-string, $delimiter-index + str-length($delimiter)); $delimiter-index: str-index($key-string, $delimiter); } $split-keys: append($split-keys, $key-string); $keys: $split-keys; } // 调用辅助函数取嵌套值 @return get-nested-value($theme-map, $keys); }
步骤3:修改theme Mixin,保留原Map结构
不再处理Map,直接把当前主题的Map赋值给$theme-map:
@mixin theme($themes) { @each $theme, $map in $themes { .theme-#{$theme} & { $theme-map: $map !global; @content; $theme-map: null !global; } } }
使用示例
现在你可以用两种方式调用函数,结果是一样的:
.past { @include theme($themes) { color: theme('text-second'); // 方式1:分隔符字符串 color: theme('text', 'second'); // 方式2:多参数 background-color: theme('button'); // 一级key同样支持 } }
两种方案各有优势:方案一调用更简洁,适合固定用-连接key的场景;方案二更灵活,支持更深层次的嵌套(比如三级、四级Map)。你可以根据自己的项目需求选择合适的方式。
内容的提问来源于stack exchange,提问作者user9024437

