变量

优质
小牛编辑
137浏览
2023-12-01

变量是任何编程语言的精髓。变量让值得以重用,避免了一遍遍地复制副本。最重要的是,使用变量让更新一个值变得很方便。不用查找、替换,更不用手动检索。

然而CSS是一个将所有鸡蛋装在一个大篮子中的语言,不同于其他语言,这里没有真正的作用域。因此,我们需要十分重视由于添加变量而引起的冲突。

我的建议只适用于创建变量并感觉确有必要的情况下。不要为了某些骇客行为而声明新变量,这丝毫没有作用。只有满足所有下述标准时方可创建新变量:

  • 该值至少重复出现了两次;
  • 该值至少可能会被更新一次;
  • 该值所有的表现都与变量有关(非巧合)。

基本上,没有理由声明一个永远不需要更新或者只在单一地方使用变量。

作用域

Sass 中变量的作用域在过去几年已经发生了一些改变。直到最近,规则集和其他范围内声明变量的作用域才默认为本地。如果已经存在同名的全局变量,则局部变量覆盖 全局变量。从 Sass 3.4 版本开始,Sass 已经可以正确处理作用域的概念,并通过创建一个新的局部变量来代替。

本部分讨论下全局变量的影子。当在局部范围内(选择器内、函数内、混合宏内……)声明一个已经存在于全局范围内的变量时,局部变量就成为了全局变量的影子。基本上,局部变量只会在局部范围内覆盖全局变量。

以下代码片可以解析变量影子的概念。

// Initialize a global variable at root level.
$variable: 'initial value';

// Create a mixin that overrides that global variable.
@mixin global-variable-overriding {
  $variable: 'mixin value' !global;
}

.local-scope::before {
  // Create a local variable that shadows the global one.
  $variable: 'local value';

  // Include the mixin: it overrides the global variable.
  @include global-variable-overriding;

  // Print the variable’s value.
  // It is the **local** one, since it shadows the global one.
  content: $variable;
}

// Print the variable in another selector that does no shadowing.
// It is the **global** one, as expected.
.other-local-scope::before {
  content: $variable;
}
// Initialize a global variable at root level.
$variable: 'initial value'

// Create a mixin that overrides that global variable.
@mixin global-variable-overriding
  $variable: 'mixin value' !global

.local-scope::before
  // Create a local variable that shadows the global one.
  $variable: 'local value'

  // Include the mixin: it overrides the global variable.
  +global-variable-overriding

  // Print the variable’s value.
  // It is the **local** one, since it shadows the global one.
  content: $variable

// Print the variable in another selector that does no shadowing.
// It is the **global** one, as expected.
.other-local-scope::before
  content: $variable

!default标识符

如果创建一个库、框架、栅格系统甚至任何的 Sass 片段,是为了分发经验或者被其他开发者使用,那么与之配置的所有变量都应该使用 !default 标志来定义,方便其他开发者重写变量。

$baseline: 1em !default;
$baseline: 1em !default

多亏如此,开发者才能在引入你的库之前定义自用的 $baseline,引入后又不必担心自己的值被重定义了。

// Developer’s own variable
$baseline: 2em;

// Your library declaring `$baseline`
@import 'your-library';

// $baseline == 2em;
// Developer’s own variable
$baseline: 2em

// Your library declaring `$baseline`
@import your-library

// $baseline == 2em

!global标识符

!global 标志应该只在局部范围的全局变量被覆盖时使用。定义根级别的变量时,!global 标志应该省略。

// Yep
$baseline: 2em;

// Nope
$baseline: 2em !global;
// Yep
$baseline: 2em

// Nope
$baseline: 2em !global

多变量或maps

使用 maps 比使用多个不同的变量有明显优势。最重要的优势就是 map 的遍历功能,这在多个不同变量中是不可能实现的。

另一个支持使用 map 的原因,是它可以创建 map-get() 函数以提供友好 API 的功能。比如,思考一下下述 Sass 代码:

/// Z-indexes map, gathering all Z layers of the application
/// @access private
/// @type Map
/// @prop {String} key - Layer’s name
/// @prop {Number} value - Z value mapped to the key
$z-indexes: (
  'modal': 5000,
  'dropdown': 4000,
  'default': 1,
  'below': -1,
);

/// Get a z-index value from a layer name
/// @access public
/// @param {String} $layer - Layer’s name
/// @return {Number}
/// @require $z-indexes
@function z($layer) {
  @return map-get($z-indexes, $layer);
}
/// Z-indexes map, gathering all Z layers of the application
/// @access private
/// @type Map
/// @prop {String} key - Layer’s name
/// @prop {Number} value - Z value mapped to the key
$z-indexes: ('modal': 5000, 'dropdown': 4000, 'default': 1, 'below': -1,)

/// Get a z-index value from a layer name
/// @access public
/// @param {String} $layer - Layer’s name
/// @return {Number}
/// @require $z-indexes
@function z($layer)
  @return map-get($z-indexes, $layer)

扩展

@extend 指令是 Sass 中既强大易于误解的指令。该指令作为一个警示,告知 Sass 对选择器 A 的样式化正好存在与选择器B共通的地方。不用多说,这是书写模块化 CSS 的好助手。

实际上,@extend 的实际作用是维护继承前后选择器之间的。这也就是说:

  • 选择器是受限的(比如:在 .foo > .bar.bar 必须有一个父级 .foo);
  • 选择器所收到的限制会传递给后续继承的选择器上(比如 .baz { @extend .bar; } 会生成 .foo > .bar, .foo > .baz);
  • 被继承的选择器会被要继承的选择器匹配。

要理解这些现象,最直接的方式就是看看当没有限制时继承样式所产生的选择器数量剧增的结果。如果 .baz .qux 继承了 .foo .bar,那么就会生成 .foo .baz .qux.baz .foo .qux,这是因为 .foo.baz 是常见的选择器,它们可以成为父级、祖父级等等。

始终使用选择器占位符定义选择器之间的关系,而不是类名,这能让你更加轻松地更换命名约定。此外,因为选择器之间的关系只被定义在了占位符中,所以很少会产生意料之外的选择器。

当继承 .class%placeholder 时,如果父类和子类是同一类型,那么建议只使用 @extend 来实现,比如 .error.warning 的一种,那么 .error 就可以通过 @extend .warning 来实现。

%button {
  display: inline-block;
  // … button styles

  // Relationship: a %button that is a child of a %modal
  %modal > & {
    display: block;
  }
}

.button {
  @extend %button;
}

// Yep
.modal {
  @extend %modal;
}

// Nope
.modal {
  @extend %modal;

  > .button {
    @extend %button;
  }
}
%button
  display: inline-block
  // … button styles

  // Relationship: a %button that is a child of a %modal
  %modal > &
    display: block

.button
  @extend %button

// Yep
.modal
  @extend %modal

// Nope
.modal
  @extend %modal

  > .button
    @extend %button

扩展选择器在许多情境下是有用和值得的。始终牢记下面这些规则,谨慎使用 @extend 指令:

  • 优先继承 %placeholders,而不是具体的选择器;
  • 当继承 .class 时,只继承单一的 .class,不要使用[复杂选择器]complex selector
  • 尽可能少的继承自 %placeholders
  • 避免继承常见的父类选择器(比如: .foo .bar)或者是常见的相邻选择器(比如:.foo ~ .bar),否则会让选择器的数量急速增加。

通常来说,@extend 有助于减少文件体积大小,因为它的操作本质上是合并选择器而不是赋值样式。话虽如此,当你使用 Gzip 压缩文件时,@extend 对于文件压缩的好处几乎是可以忽略的。

这也就是说,如果你不能使用类似 Gzip 的工具,那么就可以考虑使用 @extend 来减少不必要的重复,特别是当样式文件的大小成为性能瓶颈的时候,这种方式尤为有效。

继承和媒体查询

应该只在同一个媒体查询作用域下继承选择器,将媒体查询视为一种对作用域的限制。

%foo {
  content: 'foo';
}

// Nope
@media print {
  .bar {
    // This doesn't work. Worse: it crashes.
    @extend %foo;
  }
}

// Yep
@media print {
  .bar {
    @at-root (without: media) {
      @extend %foo;
    }
  }
}

// Yep
%foo {
  content: 'foo';

  &-print {
    @media print {
      content: 'foo print';
    }
  }
}

@media print {
  .bar {
    @extend %foo-print;
  }
}
%foo
  content: 'foo'

// Nope
@media print
  .bar
    // This doesn't work. Worse: it crashes.
    @extend %foo

// Yep
@media print
  .bar
    @at-root (without: media)
      @extend %foo

// Yep
%foo
  content: 'foo'

  &-print
    @media print
      content: 'foo print'

@media print
  .bar
    @extend %foo-print

有关 @extend 的好与坏,开发者们之间的观点大有不同,你可以阅读以下文章了解其中的利弊:

总而言之,我建议只将 @extend 用于维护选择器之间的关系。如果两个选择器是类似的,那么最好使用 @extend;如果它们之间没有关系,只是具有相同的样式,那么使用 @mixin 会更好。更多有关两者的用法,请看这篇文章:When to use extend and when to use a mixin.

感谢 David Khourshid 对本节提供的技术支持。