Sass @extend 还是 Mixin

这里总结一下 SCSS 中 @extend 和 Mixin 的几种经典用法,各自的适用场景,最佳实践。

@extend 经典用法

1, 继承样式

.message{
  border:solid 1px #ddd;
}

.error-message{
  @extend .message;
  color:red;
}

2, 简单选择器,都可以extend

a:hover{
  text-decoration: underline;
}

.hover-link{
  @extend a:hover;
}

3, 可以继承多个

.message{
  border:solid 1px #ddd;
}
.error{
  color:red;
}
.serious-error{
  //@extend .message;
  //@extend .error;
  // 也可以逗号分隔
  @extend .message, .error;

  background: mediumpurple;
}

4, 可以链式继承

.message{
  border:solid 1px #ddd;
}

.error-message{
  @extend .message;
  color:red;
}

.serious-error{
  @extend .error-message;

  background: mediumpurple;
}

5, 嵌套的关系也可以被继承

.user-link{
  color:skyblue;
  &:hover{
    text-decoration: underline;
  }
}
.comment .link{
  @extend .user-link;
}

6, 占位符,只能被继承的选择器

%highlight{
  color:red;
  font-weight: bold;
}
.notice{
  @extend %highlight;
}

7, 媒体查询指令中,不能继承外部选择器

// 正确用法
@media print, (max-width: 700px){
  %panel{
    padding:0;
  }
  .mobile-panel{
    @extend %panel
  }
}

 // 错误用法
%panel{
  padding:0;
}
@media print, (max-width: 700px){
  .mobile-panel{
    @extend %panel
  }
}

// 正确用法 2
@media (max-width: 700px){
  %panel{
    padding:0;
  }
}
// ...
@media (max-width: 700px){
  .mobile-panel{
    @extend %panel
  }
}

8, @at-root 指令,取消嵌套

// 取消嵌套
.list{
  padding: 10px;
  .item{
    float:left;
    @at-root{
      .item-tag{  font-size: 12px;  }
      .item-badge{  border-radius: 50%;  }
    }
  }
}

// 取消媒体查询的限制
@media (max-width: 700px){
  .mobile-link{
    background:lightblue;
  }

  @at-root .outer-link{
    background:skyblue;
  }

  .common-link{
    @at-root (without:media){
      background:deepskyblue;
    }
  }
}

Mixin 的经典用法

1, 重点在于复用

@mixin large-text{
  font:{
    size: 20px;
    weight:bold;
  }
  color:#333;
}

.page-title{
  @include large-text;
  padding: 10px 0;
  margin-bottom: 10px;
}
.section-title{
  @include large-text;
  margin:0;
}

2, 可以不在选择器中引入

@mixin don-not-do-this{
  a{
    color:pink;
  }
}

@include don-not-do-this;

3, 带参数用法

@mixin dash-border($color,$width){
  border:{
    color:$color;
    width:$width;
    style:dashed;
  }
}
// ...
p{
  //@include dash-border(gray,2px);

  // 更容易看清参数
  @include dash-border($color:gray,$width:2px);
}

4, 单数的默认值、列表,map,参数对象

// ## 参数默认值
@mixin dash-border($color,$width:1px){
  border:{
    color:$color;
    width:$width;
    style:dashed;
  }
}
// ...
p{
  @include dash-border(gray);
  //@include dash-border($color:gray,$width:2px);
}

// arguments

@mixin box-shadow($param...){
  -webkit-box-shadow: $param;
  box-shadow: $param;
}

.box{
  @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}

@mixin colors($text,$bg,$border){
  color:$text;
  background-color:$bg;
  border-color:$border;
}
// 列表
$value_list:#333,#666,#999;
.primary{
  @include colors($value_list...);
}
// map
$value_map:(text:#000,bg:#999,border:#666);
.secondary{
  @include colors($value_map...);
}

错误用法

1,不恰当的关联

%brand-font{
  font-family: webfont, sans-serif;
  font-weight: 700;
}
// ... 三个点代表一百行代码
h1{
  @extend %brand-font;
  font-size: 2em;
}
// ...
.btn{
  @extend %brand-font;
  display: inline-block;
  padding: 1em;
}
// ...
.promotion{
  @extend %brand-font;
  background: #bada55;
  color:#FFF;
}
// ...
.footer-message{
  @extend %brand-font;
  font-size: 0.75em;
}

2,过分的抽象

// source 264字节 css 299字节
%bold {
  font-weight: bold;
}
//...
.header--home > .header__tagline {
  @extend %bold;
  color: #333;
  font-style: italic;
}
//...
.btn--warning {
  @extend %bold;
  background-color: red;
  color: white;
}
//...
.alert--error > .alert__text {
  @extend %bold;
  color: red;
}
// 这时候应该用 Mixin
@mixin webfont() {
  font-family: webfont, sans-serif;
  font-weight: 700;
}
//...
.foo {
  @include webfont();
}
//...
.bar {
  @include webfont();
}
//...
.baz {
  @include webfont();
}

3,多层嵌套的继承

.contact-text{
  font-weight: bold;
}
.tos{
  @extend .contact-text;
}
.foot .contact-text{
  color:darkgray;
}

// 期望结果是这样么?
  .contact-text, .tos {
    font-weight: bold;
  }
  .foot .contact-text {
    color: darkgray;
  }
// 实际结果却是
  .contact-text, .tos {
    font-weight: bold;
  }
  .foot .contact-text, .foot .tos {
    color: darkgray;
  }

正确用法

  1. 没有逻辑关联的内容,不用 @extend,用Mixin
  2. 使用@extend的时候,尽量用 %placeholder,不嵌套
  3. 继承层级关系的时候,用 @extend
  4. keep sass simple

思考

1,DRY原则(Don’t repeat yourself)

Don’t repeat yourself:不是不让重复(don’t repeat)而是为了保证源头只有一个,源代码只出现在一处,一次修改自动同步到所有的CSS文件。CSS中,机器自动复制出来的的重复,不破坏 DRY 原则。

2,性能的考量

测试证明,开启gzip以后,用 Mixin 生成的重复更多,文件更大的 CSS 文件,压缩后性能更好。

附录

CSS 选择器分类 https://drafts.csswg.org/selectors-4/#syntax
4095 选择器 bug http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx
失控的代码截图 https://twitter.com/droob/status/561161783239389185 https://twitter.com/gaelmetais/status/564109775995437057