何时用 @extend 何时用 Mixin

原文链接: When to use @extend; when to use a mixin
本文已获得原作翻译授权,转载译文时请附上原文链接以及译文链接,未经允许不得随意转载译文
译文链接:何时用 @extend 何时用 mixin
翻译:于坤

何时用 @extend 何时用 mixin,这是我常被问到的一个问题,有些过时的经验说,不带参数的 mixin 不好,生成的结果中到处都有重复的代码,有点脏。事实比这个说法更微妙,下面详细说。

何时使用@extend

开门见山的说,我的建议是不要用 @extend。

就像是傻瓜的黄金(fool’s gold, pyrite, iron pyrite 黄铁矿,一个金色矿物的通称。因为黄铁矿类似于黄金的颜色,它常常被误认为是真材实料。由于黄铁矿相对一钱不值, 因此才有这个词。)它有非常多的约定和更多的注意事项。

如果你确定一定以及肯定要用 @extend,请:
1,再考虑一下
2,尽量用占位符 placeholder
3,注意编译后的内容

理论上来说,@extend 很棒,但实际应用中,太多地方容易出错了。我看过让样式文件变两倍多的;看过源码顺序被破坏;看过触发 4095 选择器 bug。

Internet Explorer 6 to 9.
– A sheet may contain up to 4095 rules
– A sheet may @import up to 31 sheets
– @import nesting supports up to 4 levels deep

IE10 (any browser/document mode):
– A sheet may contain up to 65534 rules
– A document may use up to 4095 stylesheets
– @import nesting is limited to 4095 levels (due to the 4095 stylesheet limit)

 

这类特性或者工具,带来的好处不明显,一旦出错却会造成很大麻烦的,使用时应该慎之又慎。如果你需要把样式表拆分才能避免4095个选择器bug,说明你用的这个工具非常不合理。

声明:我觉得我有必要说明一下,我不恨 @extend,只是觉得要正确使用它,要警惕,有很多注意事项。

如果你真的要用,何时使用呢?

很重要的一点,@extend 创造层级关系。使用时,你是在把别处的样式和层级关系,移植到另外的层级关系。结果所有这些选择器都产生了关联,错误的使用 @extend 会产生错误的层级。就像你用颜色排列收藏的CD一样,可行,但是这种关联没什么用。

至关重要的是,要让正确的特性产生关联。

经常有人写出这样的代码,我自己以前也这样写(假设…代表100行代码):

%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;
}

...

.promo {
@extend %brand-font;
background-color: #BADA55;
color: #fff;
}

...

.footer-message {
@extend %brand-font;
font-size: 0.75em;
}

生成的结果如下:

h1, .btn, .promo, .footer-message {
font-family: webfont, sans-serif;
font-weight: 700;
}

...

h1 {
font-size: 2em;
}

...

.btn {
display: inline-block;
padding: 1em;
}

...

.promo {
background-color: #BADA55;
color: #fff;
}

...

.footer-message {
font-size: 0.75em;
}

这里的问题是,我强制把相隔数百行,完全不相关的规则放到一起,让这些规则产生了没必要的联系。只是碰巧都用了同一段样式规则,就让代码优先级变得非常跳跃,非常不好。

把选择器移动到几百行代码之外的不恰当位置,只是为了与不相关的代码共享一段规则,这不是 @extend 的正确用法(实际上,这正是典型的不带参数的 mixin 的应用场景,后文会详细说)。

另一种 @extend 的错误用法,看起来如下:

%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;
}

产生的结果如下:

.header--home > .header__tagline,
.btn--warning,
.alert--error > .alert__text {
font-weight: bold;
}

...

.header--home > .header__tagline {
color: #333;
font-style: italic;
}

...

.btn--warning {
background-color: red;
color: white;
}

...

.alert--error > .alert__text {
color: red;
}

产生的代码有299 bytes

你本来想避免重复,结果却可能产生更长的代码。

如果每次都把这句 font-weight: bold; 写出来的话,代码只有 264 bytes。这只是一个很小的测试,但也说明了这样做收益会递减。@extend 只继承一句样式,结果可能会适得其反。

那么,到底何时使用 @extend 呢?

用到真正有联系的规则上,一个完美的用法如下:

.btn,
%btn {
display: inline-block;
padding: 1em;
}

.btn-positive {
@extend %btn;
background-color: green;
color: white;
}

.btn-negative {
@extend %btn;
background-color: red;
color: white;
}

.btn-neutral {
@extend %btn;
background-color: lightgray;
color: black;
}

生成的结果是:

.btn,
.btn-positive,
.btn-negative,
.btn-neutral {
display: inline-block;
padding: 1em;
}

.btn-positive {
background-color: green;
color: white;
}

.btn-negative {
background-color: red;
color: white;
}

.btn-neutral {
background-color: lightgray;
color: black;
}

这才是 @extend 的正确用法。css 规则有继承关系,它们共享同样的样式,是因为一样的理由,而不是因为巧合。而且,也不会让同一个选择器分散到几百行以外。

何时使用 mixin

“不带参数的 mixin 不好”这个经验,有他对的一面,但也不是这么简单。

这句规则误解了DRY原则,DRY的目标是在一个项目中保持单一真实数据源,是在说不要重复你自己,不是完全不重复。

如果你手写50次同样的声明,你是在重复你自己,这不符合DRY原则。如果你用程序生成50次,自己没有重复写,这符合DRY原则。你只是用程序生成了重复的内容,但是没有重复你自己。这是一个相当微妙但重要的区别,编译出来的重复不是坏事,在源处重复才是坏事。

单一真是数据源的意思是,把重复使用的数据源保存到一个地方,不用复制就可以回收和再利用。当然了,系统可能帮助我们重复了,但是它的来源只有一个。这意味着我们可以只修改一次,变化就自动同步到所有地方;我们的源代码没有重复,是唯一的真实数据源,这就是DRY的原则。

考虑到这一点,我们就可以想到,没有参数的 mixin 也可以非常有用。来重新看一下 %brand-font{} 这个例子。

假设有些地方必须用特定的字体和加粗:

.foo {
font-family: webfont, sans-serif;
font-weight: 700;
}

...

.bar {
font-family: webfont, sans-serif;
font-weight: 700;
}

...

.baz {
font-family: webfont, sans-serif;
font-weight: 700;
}

加粗的700不是常用的 regular 或 bold ,在代码中一遍又一遍的手写这两句声明很繁琐乏味。如果我们需要修改字体或者加粗,就需要搜遍整个项目,在每个地方都修改。

前面已经介绍了这种情况不应该用 @extend,可能我们应该做的是用 mixin:

@mixin webfont() {
font-family: webfont, sans-serif;
font-weight: 700;
}

...

.foo {
@include webfont();
}

...

.bar {
@include webfont();
}

...

.baz {
@include webfont();
}

是的,这里会产生重复,但是我们没有重复自己。这点很重要,这些规则之间没有逻辑关系,不应当产生关联。他们没有任何关系,只是碰巧都使用了同一段规则。所以这里的重复很合理。我们希望把这些声明用在 n 个地方,所以他们出现在了 n 个地方,也符合预期 。

无参数的 mixin 非常适合重复的吐出同一段规则,同时保持单一真实数据源。 这里 sass 就像在自动从剪贴板拷贝/粘贴一样。我们有单一真实数据源,我们可以只修改一次,处处生效,非常DRY。
(这里提醒一下,Gzip更适合重复的内容,所以轻微增加文件大小的缺点完全可以忽略)

当然,带参数的 mixin 非常适合生成值不固定的结构。这种用法既DRY,又保持单一真实数据源,还能自动生成,举例:

@mixin truncate($width: 100%) {
width: $width;
max-width: 100%;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.foo {
@include truncate(100px);
}

生成相同的声明,但是宽度值不同,正是 mixin 的典型用法。这是最常见,也是广泛推荐的 mixin 用法。在这点上我们意见都一致。

总结

当你想 DRY 的地方真正有继承关系,或者在主题上相关的时候,才用 @extend。不要强行把代码放到一起,这样会让代码分组不合理,源码顺序也会被打乱。

需要给相同的结构设置不同的值,或者想让 sass 代替你复制、粘贴的时候使用 mixin,这样写仍然会保持单一真实数据源。

总结的总结

@extend 有理由才用,mixin 喜欢就用。(Use @extend for same-for-a-reason; use a mixin for same-just-because.)

参考资料:

Extend 尽量用占位符 http://csswizardry.com/2014/01/extending-silent-classes-in-sass/
IE 4095 选择器 bug http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx
CSS 优先级图示 http://csswizardry.com/2014/10/the-specificity-graph/
单一真实数据源 https://en.wikipedia.org/wiki/Single_source_of_truth
tl:dr Too long; didn’t read. https://en.wiktionary.org/wiki/TL;DR

原文链接: When to use @extend; when to use a mixin
本文已获得原作翻译授权,转载译文时请附上原文链接以及译文链接,未经允许不得随意转载译文
译文链接:何时用 @extend 何时用 mixin
翻译:于坤

发布日期:
分类:CSS 标签:

作者:Yukun

前端工程师