Sass之代码复用

2,621 阅读4分钟

Sass 代码复用有3种方式

  • @mixin & @include
  • @extend
  • @function

@mixin & @include

用于复用样式逻辑,生成套路相同的样式。

使用 @mixin 定义样式模板。使用 @include 调用。

// index.scss
@mixin reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin horizontal-list {
  @include reset-list;

  li {
    display: inline-block;
    margin: {
      left: -2px;
      right: 2em;
    }
  }
}

nav ul {
  @include horizontal-list;
}
/* index.css */
nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
nav ul li {
  display: inline-block;
  margin-left: -2px;
  margin-right: 2em;
}

参数

支持传递参数。形参要与实参长度相同。

// index.scss
@mixin rtl($property, $ltr-value, $rtl-value) {
  #{$property}: $ltr-value;

  [dir=rtl] & {
    #{$property}: $rtl-value;
  }
}

.sidebar {
  @include rtl(float, left, right);
}
/* index.css */
.sidebar {
  float: left;
}
[dir=rtl] .sidebar {
  float: right;
}

可选参数

定义时使用 : 给参数添加默认值,使其可选。

默认值可以使用任何 SassScript 表达式,甚至可以使用之前的参数。

// index.scss
@mixin replace-text($image, $x: 50%, $y: 50%) {
  text-indent: -99999em;
  overflow: hidden;
  text-align: left;

  background: {
    image: $image;
    repeat: no-repeat;
    position: $x $y;
  }
}

.mail-icon {
  @include replace-text(url("/images/mail.svg"), 0);
}
/* index.css */
.mail-icon {
  text-indent: -99999em;
  overflow: hidden;
  text-align: left;
  background-image: url("/images/mail.svg");
  background-repeat: no-repeat;
  background-position: 0 50%;
}

指定参数名

传递参数时,除了可以根据位置,还可以通过指定参数名。

// index.scss
@mixin square($size, $radius: 0) {
  width: $size;
  height: $size;

  @if $radius != 0 {
    border-radius: $radius;
  }
}

.avatar {
  @include square(100px, $radius: 4px);
}
/* index.css */
.avatar {
  width: 100px;
  height: 100px;
  border-radius: 4px;
}

如果重构时需要修改参数名,为了兼容通过参数名传递的调用,可以将老参数名放到最后,并通过 @warn 警告用户迁移。

任意数量参数

为了接受任意数量的参数,在最后一个形参后需添加 ...。 传递实参时也可以在 list 类型数据后添加 ...

如果实参是根据位置传递的,则形参数据类型为 list

如果实参是通过指定参数名传递的, 通过 meta.keywords 将参数解析为 mapkey 为参数名,value 为值。 如果内部没有调用 meta.keywords,不允许使用其它指定参数名参数。

代码块

类似 Vue 的插槽。 在 @mixin 内部通过 @content 使用代码块。 在 @include 后添加 {} 注入代码块。

// index.scss
@mixin color($color) {
  .a {
    color: $color;
    @content;
  }
}

@include color($color: red) {
  &:hover {
    color: green;
  }
};
/* index.css */
.a {
  color: red;
}
.a:hover {
  color: green;
}

代码块参数

代码块也支持传入参数。参数使用同 @mixin

@include <name> using (<arguments...>) {} 调用参数

// index.scss
@mixin media($types...) {
  @each $type in $types {
    @media #{$type} {
      @content ($type);
    }
  }
}

@include media(screen, print) using ($type) {
  h1 {
    font-size: 40px;
    @if $type == print {
      font-family: Calluna;
    }
  }
}
@media screen {
  h1 {
    font-size: 40px;
  }
}
@media print {
  h1 {
    font-size: 40px;
    font-family: Calluna;
  }
}

@extend

样式设计经常有这样一种情况: 一个 class 拥有另一个 class 的所有样式,还额外包含一些样式。 在 BEM 的思想中,会在老的 class 后添加修饰符来创建一个新的 class,并将两个类全部添加到 HTML 元素中。 这将创建多余的 HTML,并将部分 CSS 逻辑带入 HTML

<div class="error error--serious">
  Oh no! You've been hacked!
</div>
.error {
  border: 1px #f00;
  background-color: #fdd;
}

.error--serious {
  border-width: 3px;
}

@extend 就是用来解决这类问题的。 继承包括3个专有名词:

  • $extender 继承者,比如 .error--serious
  • $extendee 被继承者,比如 .error
  • $selector 匹配被继承者的选择器,包括嵌套,组合选择器,伪元素等等。比如 .error.error:hover

语法如下:

$selector { ... }

#{$extender} {
  @extend #{$extendee}
}

@extend 将所有应用到 $extendee 的规则应用到 $extender 来解决上述问题。

// index.scss
.error {
  border: 1px #f00;
  background-color: #fdd;

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}
/* index.css */
.error, .error--serious {
  border: 1px #f00;
  background-color: #fdd;
}
.error--serious {
  border-width: 3px;
}

智能合并

@extend 通过将所有 $selector 的样式规则应用到 $selector$extender 合并得到的选择器来实现继承。

Sass 合并 $selector$extender 的过程非常智能。

继承发生在父选择器被编译成具体的选择器之后。

删除无用选择器

不会生成无法选中元素的选择器,比如 #main#footer

.content nav.sidebar {
  @extend .info;
}

// 不会被继承,没有既是p又是nav的元素
p.info {
  background-color: #dee9fc;
}

交叉

$selector$extender 都包含后代选择器时,为了匹配不同嵌套顺序的 HTML 元素,父级选择器会交叉,生成2个规则。

// index.scss
.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

.content nav.sidebar {
  @extend .info;
}
/* index.css .content 与 .guide 交叉 */
.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar {
  border: 1px solid rgba(0, 0, 0, 0.8);
  border-radius: 2px;
}

合并

当一个选择器匹配内容包含另一个时,只保留选择范围更小的选择器

// index.scss
.content nav.sidebar {
  @extend .info;
}

main.content .info {
  font-size: 0.8em;
}
/* index.css */
/* main.content 与 content 合并,只保留main.content */
main.content .info, main.content nav.sidebar {
  font-size: 0.8em;
}

优先级

精简选择器的同时确保特异性大于等于 $extender 的特异性。

其它

智能地处理组合选择器,全局选择器和伪类。

// index.scss
.error:hover {
  background-color: #fee;
}

.error--serious {
  @extend .error;
  border-width: 3px;
}
/* index.css 处理 hover 伪类*/
.error:hover, .error--serious:hover {
  background-color: #fee;
}

.error--serious {
  border-width: 3px;
}

占位选择器(placeholder selector)

% 开头命名的选择器,专门用于继承,不会生成 CSS 代码。支持私有。

// index.scss
.alert:hover, %strong-alert {
  font-weight: bold;
}

%strong-alert:hover {
  color: red;
}
/* index.css */
.alert:hover {
  font-weight: bold;
}

继承范围

通过 @use@forward引用文件,可以继承被引用文件和被递归引用文件中的选择器。 ,非引用文件中的选择器不可继承。

A @use B1, C; B1 @use B2; B2 @use B3B1 可以继承 B2B3 中的选择器。 尽管最终 B1C 的样式会合并到 A,但是 B1C 无引用关系,不能继承。

通过 @import 引用,则可以继承所有的选择器。

可选

如果找不到对应 $extendee$selector,编译时会报错。 这能避免拼写错误导致的问题。 如果不想使用这个特性,在继承后添加 !optional

局限

  1. 只能继承简单独立的选择器,比如 .infoa。不能继承 .message.info
  2. 合并交叉时,只生成两个,不会遍历所有父类交叉的可能
  3. CSS @rules 内部不能继承外部的样式。

相关内置函数

selector.unify

计算两个选择器合并的结果。

@debug selector.unify("a", ".disabled"); // a.disabled
@debug selector.unify("a.disabled", "a.outgoing"); // a.disabled.outgoing
@debug selector.unify("a", "h1"); // null
@debug selector.unify(".warning a", "main a"); // .warning main a, main .warning a

selector.extend

计算继承后生成的选择器。

selector.extend($selector, $extendee, $extender)

@debug selector.extend("a.disabled", "a", ".link"); // a.disabled, .link.disabled
@debug selector.extend("a.disabled", "h1", "h2"); // a.disabled
@debug selector.extend(".guide .info", ".info", ".content nav.sidebar");
// .guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar

@function

封装数据的复杂运算,生成值。最好只用作纯函数。 使用 @function 定义,内部使用 @return 返回值。

// index.scss
@function pow($base, $exponent) {
  $result: 1;
  @for $_ from 1 through $exponent {
    $result: $result * $base;
  }
  @return $result;
}

.sidebar {
  float: left;
  margin-left: pow(4, 3) * 1px;
}
/* index.css */
.sidebar {
  float: left;
  margin-left: 64px;
}

参数

语法同 @mixin

其它函数

  • Sass 内部提供了许多内置函数,用于处理各种类型的数据。
  • CSS 函数,不会被编译,但是参数可以被编译。

对比

比较

@mixin@function@extend
接收参数✔️✔️
生成样式✔️✔️
生成值✔️
修改参数✔️✔️

适用场景

  • 如果要生成值,使用 @function
  • 如果要生成的样式与其他类在语义上具有继承关系,使用 @extend
  • 只用来修改变量使用 @mixin。虽然 @function 可以修改变量,但是最好只用做纯函数。
  • 如果要生成样式,且需要接受参数,使用 @mixin
  • 其他情况使用 @mixin