何时用 @extend 何时用 mixin,这是我常被问到的一个问题,有些过时的经验说,不带参数的 mixin 不好,生成的结果中到处都有重复的代码,有点脏。事实比这个说法更微妙,下面详细说。
开门见山的说,我的建议是不要用 @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 不好”这个经验,有他对的一面,但也不是这么简单。
这句规则误解了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