Sass Guidelines中文版本之六:变量、扩展、混合宏

Sass Guidelines已经整理发布了五个部分,从其相关信息简介命名约定与注释项目文件管理到第五部分的响应式设计与断点管理,作者都做出相关总结与推荐性建议。这篇文章是其第六个部分,主要涵盖了Sass中的变量、扩展、混合宏和控制指令等相关的知识,希望大家会喜欢。

变量

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

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

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

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

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

作用域

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

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

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

// Initialize a global variable at root level.
// In this case, the `!global` flag is optional.
$variable: 'initial value' !global;
// 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;
}

!default标志

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

$baseline: 1em !default;

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

// Developer's own variable
$baseline: 2em;
// Your library declaring `$baseline`
@import 'your-library';
// $baseline == 2em;

!global标志

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

// 推荐方式
$baseline: 2em;
// 不推荐方式
$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);
}

扩展

@extend指令是几年前使Sass风靡一时的重要特性之一。该指令作为一个警示,告知Sass对元素A的样式化正好存在与选择器B共通的地方。不用多说,这是书写模块化CSS的好助手。

然而我感觉必须提醒你谨慎使用这个特性。正因灵活多变,所以@extend还是一个棘手的概念,某些时候可能弊大于利,特别是被滥用时。当扩展一个选择器时,除非你对整个代码库有深入的了解,不然肯定没法回答诸如下面的问题:

  • 当前选择器要追加到哪里?
  • 我是否会碰上意料之外的副作用?
  • 这条简单的扩展将会产生多大的CSS?

如你所知,结果的可能即包括没有任何影响,也包括灾难性副作用。因此,我的第一建议是完全避免使用@extend指令。这听起来有些残酷,但最终会拯救你于水火之中。

俗话说的好:

永远不要说不可能。——Apparently, not Beyonce

扩展选择器在有些情境下是有用和值得的。始终牢记这些规则,那样就不会是自己陷入困境:

  • 从单一模块扩展,而不是多个不同的模块;
  • 只使用占位符扩展,而不使用实际的选择器;
  • 确保用来扩展的占位符是样式表中尽可能小的。

如果你要使用扩展,我想提醒你它也不能与@media块元素融洽相处。如你所知,Sass不能在一个媒体查询中扩展外部的选择器。当你这么做的时候,编译器轻易就会崩溃,并提醒你不能这么干。这不是好消息,特别是几乎所有人都知道媒体查询功能。

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

无法在@media内部@extend一个外部选择器。只可以使用相同指令@extend选择器。

这就是常说的@extend可以通过合并选择器而不是复制属性帮助减小文件大小。这种说法是对的,不过一旦经Gzip处理后这点差异完全可以忽略不计。也就是说,如果你无法使用Gzip(或相同工具),在你足够精通的前提下使用@extend方式也是不错的。

总而言之,除非在某些特殊情况下,否则我建议不要使用@extend指令,但我不会禁用它。

扩展阅读

混合宏

混合宏是整个Sass语言中最常用的功能之一。这是重用和减少重复组件的关键。这么做有很棒的原因:混合宏允许开发者在样式表中定义可复用样式,减少了对非语义类的需求,比如.float-left

它们可以包含所有的CSS规则,并且在Sass文档允许的任何地方都表现良好。它们甚至可以像函数一样接受参数。不用多说,充满了无尽的可能。

不过我有必要提醒你滥用混合宏的破坏力量。再次重申一遍,使用混合宏的关键是简洁。建立混入大量逻辑而极具力量的混合宏看上去确实很有诱惑力。这就是所谓的过度开发,大多数开发者常常因此陷入困境。不要过度逻辑化你的代码,尽量保持一切简洁。如果一个混合宏最后超过了20行,那么它应该被分离成更小的块甚至是重建。

基础

话虽如此,混合宏确实非常有用,你应该学习使用它。经验告诉我们,如果你发现有一组CSS属性经常因同一个原因一起出现(非巧合),那么你就可以使用混合宏来代替。比如Nicolas Gallagher的清除浮动应当放入一个混合宏的实例。

/// Helper to clear inner floats
/// @author Nicolas Gallagher
/// @link http://nicolasgallagher.com/micro-clearfix-hack/ Micro Clearfix
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

另一个有效的实例是通过在混合宏中绑定widthheight属性,可以为元素设置宽高。这样不仅会淡化不同类型代码间的差异,也便于阅读。

/// Helper to size an element
/// @author Hugo Giraudel
/// @param {Length} $width
/// @param {Length} $height
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}

扩展阅读

参数列表

当混合宏需要处理数量不明的参数时,通常使用arglist而不是列表。可以认为arglist是Sass中隐藏而未被记录的第八个数据类型,通常当需要任意数量参数的时候,被隐式使用到参数中含有...标志的混合宏和函数中。

@mixin shadows($shadows...) {
  // type-of($shadows) == 'arglist'
  // ...
}

现在,当要建立一个接收多个参数(默认为3或者更多)的混合宏时,在将它们合并为列表或者map之前,要反复考量这样做是否比一个个的单独存在更易于使用。

Sass的混合宏和函数声明非常智能,你只需给函数/混合宏一个列表或map,它会自动解析为一系列的参数。

@mixin dummy($a, $b, $c) {
  // ...
}
// 推荐方式
@include dummy(true, 42, 'kittens');
// 不推荐方式
$params: true, 42, 'kittens';
$value: dummy(nth($params, 1), nth($params, 2), nth($params, 3));
// 推荐方式
$params: true, 42, 'kittens';
@include dummy($params...);
// 推荐方式
$params: (
  'c': 'kittens',
  'a': true,
  'b': 42
);
@include dummy($params...);

扩展阅读

混合宏和浏览器前缀

通过使用自定义混合宏来处理CSSS中未被支持或部分支持的浏览器前缀,是非常有吸引力的一种做法。但我们不希望这么做。首先,如果你可以使用Autoprefixer,那就使用它。它会从你的项目中移除Sass代码,会一直更新并一定会进行比你手动添加前缀更棒的处理。

不行的是,Autoprefixer并不是总被支持的。如果你使用BourbonCompass,你可能就已经知道它们都提供了一个混合宏的集合,用来为你处理浏览器前缀,那就用它们吧。

如果你不能使用Autoprefixe,甚至也不能使用Bourbon和Compass,那么接下来唯一的方式,就是使用自己的混合宏处理带有前缀的CSS属性。但是,请不要为每个属性建立混合宏,更不要无脑输出每个浏览器的前缀(有些根本就不存在)。

// 不推荐方式
@mixin transform($value) {
  -webkit-transform: $value;
  -moz-transform: $value;
  transform: $value;
}

比较好的做法是

/// Mixin helper to output vendor prefixes
/// @access public
/// @author HugoGiraudel
/// @param {String} $property - Unprefixed CSS property
/// @param {*} $value - Raw CSS value
/// @param {List} $prefixes - List of prefixes to output
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    -#{$prefix}-#{$property}: $value;
  }
  #{$property}: $value;
}

然后就可以非常简单地使用混合宏了:

.foo {
  @include prefix(transform, rotate(90deg), webkit ms);
}

请记住,这是一个糟糕的解决方案。例如,他不能处理那些需要复杂的前缀,比如flexbox。在这个意义上说,使用Autoprefixer是一个更好地选择。

扩展阅读

条件语句

你可能早已知晓,Sass通过@if@else指令提供了条件语句。除非你的代码中有偏复杂的逻辑,否则没必要在日常开发的样式表中使用条件语句。实际上,条件语句主要适用于库和框架。

无论何时,如果你感觉需要它们,请遵守下述准则:

  • 除非必要,不然不需要括号;
  • 务必在左开大括号({)后换行;
  • @else语句和它前面的右闭大括号(})写在同一行。
// 推荐方式
@if $support-legacy {
  // ...
} @else {
  // ...
}

// 不推荐方式
@if ($support-legacy == true) {
  // ...
}
@else {
  // ...
}

测试一个错误值时,通常使用not关键字而不是比较与falsenull等值。

// 推荐方式
@if not index($list, $item) {
  // ...
}
// 不推荐方式
@if index($list, $item) == null {
  // ...
}

当使用条件语句并在一些条件下有内联函数返回不同结果时,始终要确保最外层函数有一个@return语句。

// 推荐方式
@function dummy($condition) {
  @if $condition {
    @return true;
  }
  @return false;
}
// 不推荐方式
@function dummy($condition) {
  @if $condition {
    @return true;
  } @else {
    @return false;
  }
}

本文根据Hugo Giraudel的《Sass Guidelines》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:Sass Guidelines

南北

在校学生,本科计算机专业。狂热地想当一名作家,为色彩和图形营造的视觉张力折服,喜欢调教各类JS库,钟爱CSS,希望未来加入一个社交性质的公司,内心极度肯定“情感”在社交中的决定性地位,立志于此改变社交关系的快速迭代。

如需转载,烦请注明出处:http://www.w3cplus.com/preprocessor/sass-guidelin-part-6.html

返回顶部