何时该使用 继承,何时该使用混合?

79 阅读10分钟

译者:HappyAdu

原文链接

20 November, 2014

何时该使用 @extend,何时该使用mixin

这是一个我曾被问到过许多次的问题:在什么时候我们应该使用mixin(混合),而在什么时候我们应该使用@extend(继承)呢?

有一条经验法则,说的是没有参数的mixin是不好的。换句话说,完全相同、没有差别的mixin简直就是儿戏。而事实上问题远比这复杂得多。

现在,让我们一起看一看.

何时使用继承 @extend

这里让我先说一句:我通常建议一点也不要使用继承开始 @extend 。这里有一个 Fool’s Gold(傻瓜黄金定律):一个特性的允诺就必然携带着两倍多的警告(译者注:我理解的是一个强大的功能就必然携带着两倍多的弊端,或者一倍收益,两倍风险。)。

如果你肯定, 确确实实要使用继承 @extend:

  1. 请再三考虑.

  2. 使用placeholder hack.

  3. 时刻关注你输出的css文件.

理论上, @extend 是很棒的方法, 然而, 事实证明, 有许多使用出现了错误。我曾见过一个输出样式表是原文件大小的两倍多;我也曾见过一份源程序输出文件变得混论不堪; 并且我还曾看见过一个编译后的样式的群组选择器有4095个选择器。最好总是宁可谨慎并且省略任何会造成很多麻烦但是收益却很少或者甚至没有的功能或者工具。由于滥用生产工具让你的碎片样式享有有低于4096选择器组是十分违反直觉的。

**N.B.**我觉得我需要补充说一下这并不是我本身就讨厌 @extend ;但如果你要使用它,一定要时刻保持警惕,因为有很多地方需要注意。

但是,如果你要使用 @extend,何时适合呢?

理解 @extend创建的关系就很重要了。无论何时你使用@extend,移植一个选择器就会在在其他地方为了与其他选择器共享特征也因此被移植了。结果,你导致这些选择器全部共享了一个关系,并滥用@extend创建关系制定了错误的标准。 这就像分组CD收集的颜色封面一样:可行,但是并不是创建的一个有益的关系。

你正在形成的这种关系是一个正确的特征是至关重要的。

很频繁的——在过去,我自己也犯过——我看到的许多东西像这样(让我想一下...表示一个遗漏,简单的说,第100行,(译者注:即输出文件的21行)):

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

问题是我强迫了一个存在于几百行开外的没有丝毫关联的规则和当前规则因为共享特征建立了一个纯粹是巧合的联系。并且这不仅强迫生成了一个反常的关系,还使得生成的CSS选择器的顺序特异性变得乱七八糟。我竟然因为纯粹的间接关系分散了我的代码库的选择器。这并不是一个好消息.

在基于纯粹的巧合和间接地相似之处的情况下,继承使得我为了和其他规则集共存在一个不正确的位置,移植了一个不相关的规则集到远离它们来源几百行开外的地方。使用@extend并不是一个好方法. (实际上,使用一个带有少量参数的mixin可能是一个完美的用例,我们很快会讲到这个话题。)

另一个滥用继承的情况看起来有点像这样:

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

如你所愿,它将输出下面的css代码:

.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个字节大小。

通常情况下, 你移植的选择器可能会比你试图避免重复的声名还要长。

如果我们真的只是重复font-weight: bold规则;声明多次——而不是试图避免重复它——结果我们确实获得了一个比较小的css文件:264 个字节。这仅仅是规模非常小的模型,但是这有助于说明收益递减的可能性。继承的单一声明往往是适得其反的。

所以,何时我们使用继承呢?

我们应该使用继承在明确相关的规则集中共享特性,一个完美的用例如下:

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

Which results in:

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

这是一个完美的继承用例。这些规则本质上是相关的;他们共享的特征是由于一个理由共享的,而不是偶然性的。进一步的说,我们不会再移植这些选择器到远离其来源上百行开外的地方去了,所以我们的 特异性图能够保持漂亮和条理性.

何时该使用混合mixin

不带参数的混合是糟糕的规则这条古老的经验法则是善意的,但不幸的是这远没有那么简单。

这条规则源于对 DRY(译者注:DRY是“Don't Repeat Yourself”的缩写)原则一点轻微的误解。DRY原则旨在一个项目只有一个真正的单一来源。DRY与不重复自身相关,但是并不是完全避免重复。

如果你在一个项目中手动键入一个声明50次,你是重复的:这不是DRY。 如果你生成50次声明不用手动键入,这就是DRY:你在生成重复而不是重复自身。这是一个相当微妙但重要的区别. 在编译系统的重复不是一件坏事,在文件资源(来源)方面的重复才是一件坏事。

这条单一的真理意味着我们可以存储一个重复结构的资源回收再利用而不是实际上去复制它。当然,一个系统可能会重复给我们,但是它的资源只存在一次。这意味着我们只需改变资源一次,其他利用到该资源的地方就会随着改变;而在我们的源代码中不会有重复的结构;只会有一个实际的单一来源。这就是当我们谈到DRY时的意思。

有了这一理解,我们就能开始意识到不带参数的mixin实际上是有用的了。现在让我们回到之前%brand-font {}的例子

让我们想象一下,我们使用一个特定的字体在我们的项目,必须始终在旁边定义一个字体特定的 font-weight:

.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;并且如果我们要改变web的字体和粗细程度,我们不得不浏览整个项目更改所有地方的web字体和字体粗细程度。

我们前面不应该在这里使用@extend强行关联,我们也许应该使用的是一个mixin:

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

...

.foo {
    @include webfont();
}

...

.bar {
    @include webfont();
}

...

.baz {
    @include webfont();
} 

是的,这会编译出重复的部分。不,我们不是在重复自己。 要记住这些无关的规则集是十分重要的,所以我们不应该去使他们相关。要记住这些无关重要的规则,所以我们不应该让他们相关的。他们是无关的,只是_happen_有一些共同特征,所以这种重复是明智的,而且是可以预期的。我们_want_在N的地方使用这些声明,所以我们让他们出现在n处。It is important to remember here that these are unrelated rulesets, so we did not ought to make them related. They are unrelated and just happen to have some shared traits, so this repetition is sensible, and is to be expected. We want to use those declarations in n places, so we make them appear in n places.

Argument-less mixins are great for just spitting out repeated groups of identical declarations whilst maintaining a Single Source of Truth. See it like a Sassy extension of your copy/paste clipboard: you’re just using it to paste out a few strings you’ve stored elsewhere earlier on. We have our Single Source of Truth, which means we can propagate changes to these declarations whilst only ever making one manual change. Very DRY.

它也可能是值得注意的,有利于重复,gzip,将几乎完全否定的轻微的成本增加文件大小It is also probably worth noting that Gzip favours repetition, so that will almost entirely negate the costs of the slight added filesize.

当然,mixin是真的有用当我们使用带参数的mixin时,它会在重复的代码结构中动态的生成值。我不认为任何人都可以说它是一个坏的主意:他们不会重复自身但是允许我们修改这些重复结构的单一来源从而像坐飞机一样改变所有引用该结构的地方。例如:

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

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

吐出相同的声明,但根据实际案例动态地设置了“宽度”的值。

这是最常见的和广泛认同的形式的混合,我想我们都同意这是一个好主意。

tl;dr

当你试图使用DRY原则的规则集是本质上和主题相关时仅仅使用@extend。不要强迫不存在的关系,这样做会在你的项目中创造出不寻常的分组,同时也会给你的源代码造成负面影响。

使用mixin要么注入动态值代入重复的结构中(译者注:此处指的是带参数的mixin),要么如时髦的复制/粘贴般(译者注:指不带参数的mixin)在你的整个项目中重复一组声明同时保持只有一个来源(译者注:此处可以解释为只有一个引用来源引用).

tl;dr;tl;dr

与本身相关的规则集是使用 @extend的理由;相同的构造结构仅仅是使用mixin的缘由。