地图中的深度获取/设置

Avatar of Kitty Giraudel
Kitty Giraudel on

在处理复杂的 Sass 架构时,使用 Sass 地图来维护配置和选项并不少见。有时你会看到地图嵌套在其他地图中(可能在多个层级上),就像 o-grid 中的这个例子

$o-grid-default-config: (
    columns: 12,
    gutter: 10px,
    min-width: 240px,
    max-width: 1330px,
    layouts: (
        S:  370px,  // ≥20px columns
        M:  610px,  // ≥40px columns
        L:  850px,  // ≥60px columns
        XL: 1090px  // ≥80px columns
    ),
    fluid: true,
    debug: false,
    fixed-layout: M,
    enhanced-experience: true
);

这类地图的问题在于,从嵌套的树中获取和设置值并不容易。这绝对是你想隐藏在函数中的东西,以避免每次都手动进行。

深度获取

实际上,构建一个函数来从地图中获取深度嵌套的值非常容易。

/// Map deep get
/// @author Kitty Giraudel
/// @access public
/// @param {Map} $map - Map
/// @param {Arglist} $keys - Key chain
/// @return {*} - Desired value
@function map-deep-get($map, $keys...) {
    @each $key in $keys {
        $map: map-get($map, $key);
    }
    @return $map;
}

例如,如果我们想获取与我们配置地图中的 M 布局关联的值,只需像这样操作:

$m-breakpoint: map-deep-get($o-grid-default-config, "layouts", "M");
// 610px

注意,字符串周围的引号是可选的。我们只是为了可读性而添加它们。

深度设置

另一方面,构建一个函数来设置深度嵌套的键可能会非常繁琐。

/// Deep set function to set a value in nested maps
/// @author Kitty Giraudel
/// @access public
/// @param {Map} $map - Map
/// @param {List} $keys -  Key chaine
/// @param {*} $value - Value to assign
/// @return {Map}
@function map-deep-set($map, $keys, $value) {
  $maps: ($map,);
  $result: null;
  
  // If the last key is a map already
  // Warn the user we will be overriding it with $value
  @if type-of(nth($keys, -1)) == "map" {
    @warn "The last key you specified is a map; it will be overrided with `#{$value}`.";
  }
  
  // If $keys is a single key
  // Just merge and return
  @if length($keys) == 1 {
    @return map-merge($map, ($keys: $value));
  }
  
  // Loop from the first to the second to last key from $keys
  // Store the associated map to this key in the $maps list
  // If the key doesn't exist, throw an error
  @for $i from 1 through length($keys) - 1 {
    $current-key: nth($keys, $i);
    $current-map: nth($maps, -1);
    $current-get: map-get($current-map, $current-key);
    @if $current-get == null {
      @error "Key `#{$key}` doesn't exist at current level in map.";
    }
    $maps: append($maps, $current-get);
  }
  
  // Loop from the last map to the first one
  // Merge it with the previous one
  @for $i from length($maps) through 1 {
    $current-map: nth($maps, $i);
    $current-key: nth($keys, $i);
    $current-val: if($i == length($maps), $value, $result);
    $result: map-merge($current-map, ($current-key: $current-val));
  }
  
  // Return result
  @return $result;
}

现在,如果我们想更新与我们配置地图中的 M 布局关联的值,我们可以执行以下操作:

$o-grid-default-config: map-deep-set($o-grid-default-config, "layouts" "M", 650px);

额外资源

上面的函数并不是解决这个问题的唯一方案。

Sassy-Maps 库还提供了 map-deep-setmap-deep-get 函数。同样,Kitty Giraudel 也编写了一个 jQuery 风格的 extend 函数 来使内置的 map-merge 递归,并且能够一次合并多个地图。