在网页开发行业工作了 14 年多,我见过也写过不少好坏参半的 CSS 代码。五年前,当我开始在 Ramsey Solutions 工作时,接触到了 Sass。它强大的功能让我感到震惊!我立即投入其中,并渴望学习关于它的所有知识。在过去的五年里,我使用了许多不同的 Sass 技术和模式,并爱上了一些,借用苹果公司的一句话来说,就是好用的技术。
在这篇文章中,我将探讨广泛的主题
根据我的经验,找到简单和复杂之间的平衡是开发优秀软件的关键。软件不仅应该易于用户使用,还应该易于您和其他开发人员在未来进行维护。我将这些技术视为高级技术,但并非故意使其变得巧妙或复杂!
“每个人都知道,调试比编写程序本身要困难一倍。因此,如果您在编写程序时尽可能地聪明,那么您将如何调试它呢?”
——《编程与风格要素》(第二版),第二章
考虑到这一点,让我们首先看看 Sass 的与符号。
与符号的强大功能
您可以使用许多不同的命名约定来组织您的 CSS。我最喜欢使用的是 SUIT,它是 BEM(即Block、Element、Modifier 的缩写)的一种变体。如果您不熟悉 SUIT 或 BEM,我建议您在继续之前先了解一下它们。在本文的其余部分,我将使用 SUIT 约定。
无论您选择哪种命名约定,基本思想都是每个样式化元素都获得自己的类名,并在类名前加上组件名称。这个想法对于以下一些组织方式的运作至关重要。此外,本文仅描述性,而非规定性。每个项目都不同。您需要根据您的项目和团队的需求选择最合适的方法。
我之所以喜欢使用 SUIT、BEM 和类似的约定,主要原因在于 与符号。它允许我在不产生选择器冲突的情况下使用嵌套和作用域。举个例子,如果不使用与符号,我需要创建单独的选择器来创建-title
和-content
元素。
.MyComponent {
.MyComponent-title {}
}
.MyComponent-content {}
// Compiles to
.MyComponent .MyComponent-title {} // Not what we want. Unnecessary specificity!
.MyComponent-content {} // Desired result
在使用 SUIT 时,我希望-content
的第二个结果成为我编写所有选择器的方式。为此,我需要在整个过程中重复组件的名称。这增加了我在编写新样式时打错组件名称的可能性。它也很冗长,因为它最终会忽略许多选择器的开头,这可能导致忽略明显的错误。
.MyComponent {}
.MyComponent-title {}
.MyComponent-content {}
.MyComponent-author {}
// Etc.
如果这是普通的 CSS,我们会被迫编写上述代码。由于我们使用的是 Sass,因此有一种更好的方法可以使用与符号。与符号非常棒,因为它包含对当前选择器以及任何父级的引用。
.A {
// & = '.A'
.B {
// & = '.A .B'
.C {
// & = '.A .B .C'
}
}
}
您可以在上面的示例中看到,与符号如何在嵌套代码中深入时引用链中的每个选择器。通过利用此功能,我们可以创建新的选择器,而无需每次都重写组件的名称。
.MyComponent {
&-title {}
&-content {}
}
// Compiles to
.MyComponent {}
.MyComponent-title {}
.MyComponent-content {}
这很棒,因为我们可以利用与符号来编写一次组件的名称,然后在整个过程中简单地引用组件名称。这降低了组件名称被错误输入的可能性。此外,整个文档也更容易阅读,而无需在代码中重复.MyComponent
。
有时,组件需要一个变体或修饰符,就像在 SUIT 和 BEM 中所说的那样。使用与符号模式可以更容易地创建修饰符。
<div class="MyComponent MyComponent--xmasTheme"></div>
.MyComponent {
&--xmasTheme {}
}
// Compiles to
.MyComponent {}
.MyComponent--xmasTheme {}
“但是,如何修改子元素呢?”您可能会问。“这些选择器是如何创建的?修饰符并不需要在每个元素上使用,对吧?”
这就是变量可以提供帮助的地方!
变量和作用域
过去,我曾以几种不同的方式创建修饰符。大多数时候,我会重写我想在修改元素时应用的特殊主题名称。
.MyComponent {
&-title {
.MyComponent--xmasTheme & {
}
}
&-content {
.MyComponent--xmasTheme & {
}
}
}
// Compiles to
.MyComponent-title {}
.MyComponent--xmasTheme .MyComponent-title {}
.MyComponent-content {}
.MyComponent--xmasTheme .MyComponent-content {}
这可以完成工作,但我又回到了在多个地方重写组件名称,更不用说修饰符名称了。当然有更好的方法。让我们看看 Sass 变量。
在探索 Sass 变量与选择器之前,我们需要了解它们的作用域。Sass 变量具有作用域,就像在 JavaScript、Ruby 或任何其他编程语言中一样。如果在选择器外部声明,则该变量在声明之后可用于文档中的每个选择器。
$fontSize: 1.4rem;
.a { font-size: $fontSize; }
.b { font-size: $fontSize; }
在选择器内部声明的变量的作用域仅限于该选择器及其子元素。
$fontSize: 1.4rem;
.MyComponent {
$fontWeight: 600;
font-size: $fontSize;
&-title {
font-weight: $fontWeight; // Works!
}
}
.MyComponent2 {
font-size: $fontSize;
&-title {
font-weight: $fontWeight; // produces an "undefined variable" error
}
}
我们知道变量可以存储字体名称、整数、颜色等。您知道它还可以存储选择器吗?使用字符串插值,我们可以使用变量创建新的选择器。
// Sass string interpolation syntax is #{VARIABLE}
$block: ".MyComponent";
#{$block} {
&-title {
#{$block}--xmasTheme & {
}
}
}
// Compiles to
.MyComponent {}
.MyComponent-title {}
.MyComponent--xmasTheme .MyComponent-title {}
这很酷,但变量是 全局作用域的。我们可以通过在组件声明内部创建$block
变量来解决这个问题,这将将其作用域限制在该组件中。然后,我们可以在其他组件中重复使用$block
变量。这有助于使主题修饰符更加 DRY。
.MyComponent {
$block: '.MyComponent';
&-title {
#{$block}--xmasTheme & {
}
}
&-content {
#{$block}--xmasTheme & {
}
}
}
// Compiles to
.MyComponent {}
.MyComponent-title {}
.MyComponent--xmasTheme .MyComponent-title {}
.MyComponent-content {}
.MyComponent--xmasTheme .MyComponent-content {}
这更接近了,但我们仍然必须一遍遍地写主题名称。让我们也将其存储在变量中!
.MyComponent {
$block: '.MyComponent';
$xmasTheme: '.MyComponent--xmasTheme';
&-title {
#{$xmasTheme} & {
}
}
}
这好多了!但是,我们还可以进一步改进它。变量还可以存储与符号的值!
.MyComponent {
$block: &;
$xmasTheme: #{&}--xmasTheme;
&-title {
#{$xmasTheme} & {
}
}
}
// Still compiles to
.MyComponent {}
.MyComponent-title {}
.MyComponent--xmasTheme .MyComponent-title {}
现在这才是我想要的!使用与符号“缓存”选择器允许我们在顶部创建修饰符,并将主题修改与它所修改的元素一起保留。
“当然,这在顶层有效,”你说。“但是如果你嵌套得很深,比如嵌套了八层呢?”你问的问题很棒。

无论嵌套深度如何,此模式始终有效,因为主组件名称从未附加到任何子元素上,这要归功于 SUIT 命名约定和与符号组合。
.MyComponent {
$block: &;
$xmasTheme: #{&}--xmasTheme;
&-content {
font-size: 1.5rem;
color: blue;
ul {
li {
strong {
span {
&::before {
background-color: blue;
#{$xmasTheme} & {
background-color: red;
}
}
}
}
}
}
}
}
// Compiles to
.MyComponent-content {
font-size: 1.5rem;
color: blue;
}
.MyComponent-content ul li strong span::before {
background-color: blue;
}
/*
* The theme is still appended to the beginning of the selector!
* Now, we never need to write deeply nested Sass that's hard to maintain and
* extremely brittle: https://css-tricks.cn/sass-selector-combining/
*/
.MyComponent--xmasTheme .MyComponent-content ul li strong span::before {
background-color: red;
}
代码组织是我喜欢使用此模式的主要原因。
- 它相对 DRY
- 它支持“选择加入”方法,该方法将修饰符与其修改的元素一起保留
- 命名事物很难,但这使我们能够重用常见的元素名称,如“title”和“content”
- 通过将修饰符类放在父组件上,添加修饰符到组件的成本很低
“嗯……但是创建了许多不同的组件后,这是否会变得难以阅读?当所有内容都命名为&-title
和&-content
时,您如何知道自己在哪里?”
您继续问一些很棒的问题。谁说源Sass 必须在一个文件中?我们可以导入这些组件,所以让我们转向这个话题!
导入的重要性

Sass 最好的功能之一是@import
。我们可以创建单独的 Sass 文件(部分文件)并将它们导入到其他 Sass 文件中,这些文件与导入的文件一起编译,导入的文件位于其导入的位置。这使得将相关的组件、实用程序等的样式打包在一起并将其提取到单个文件中变得容易。如果没有@import
,我们需要链接到单独的 CSS 文件(创建大量网络请求,这 不好)或在单个样式表中编写所有内容(这很难导航和维护)。
.Component1 {
&-title {}
&-content {}
&-author {}
}
.Component2 {
&-title {}
&-content {}
&-author {}
}
.Component3 {
&-title {}
&-content {}
&-author {}
}
.Component4 {
&-title {}
&-content {}
&-author {}
}
.Component5 {
&-title {}
&-content {}
&-author {}
}
// A couple of hundred lines later...
.Component7384 {
&-title {}
&-content {}
&-author {}
}
// WHERE AM I?
组织 Sass 文件的一种更流行的方法是 7-1 模式。即七个不同的文件夹包含 Sass 文件,这些文件被导入到单个 Sass 文件中。
这些文件夹是
- 抽象
- 基础
- 组件
- 布局
- 页面
- 主题
- 供应商
使用@import
将这些文件夹中的每个 Sass 文件提取到一个主 Sass 文件中。我们希望按照以下顺序导入它们,以保持良好的作用域并在编译期间避免冲突
- 抽象
- 供应商
- 基础
- 布局
- 组件
- 页面
- 主题
@import 'abstracts/variables';
@import 'abstracts/functions';
@import 'abstracts/mixins';
@import 'vendors/some-third-party-component';
@import 'base/normalize';
@import 'layout/navigation';
@import 'layout/header';
@import 'layout/footer';
@import 'layout/sidebar';
@import 'layout/forms';
@import 'components/buttons';
@import 'components/hero';
@import 'components/pull-quote';
@import 'pages/home';
@import 'pages/contact';
@import 'themes/default';
@import 'themes/admin';
您可能希望或可能不希望使用所有这些文件夹(我个人不使用主题文件夹,因为我将主题与其组件一起保留),但是将所有样式分离到不同的文件中这一想法使代码更容易维护和查找。
使用此方法的更多好处
- 小组件更容易阅读和理解
- 调试变得更简单
- 更清楚地确定何时应创建新组件——例如,当单个组件文件变得太长或选择器链过于复杂时
- 这强调了重用——例如,将三个本质上执行相同操作的组件文件概括为一个组件可能是有意义的
说到重用,最终会有一些经常使用的模式。这时我们可以使用混合。
混合(Mixin)的使用
混合是整个项目中重用样式的好方法。让我们逐步创建简单的混合,然后为其添加一些智能。
我经常合作的设计师总是将font-size
、font-weight
和line-height
设置为特定值。我发现每次需要调整组件或元素的字体时,都要手动输入这三个属性,因此我创建了一个mixin来快速设置这些值。它就像一个小的函数,我可以使用它来定义这些属性,而无需完整地编写它们。
@mixin text($size, $lineHeight, $weight) {
font-size: $size;
line-height: $lineHeight;
font-weight: $weight;
}
目前,这个mixin非常简单——类似于JavaScript中的函数。它有一个mixin名称(text
),并接收三个参数。每个参数都与一个CSS属性关联。当调用mixin时,Sass会复制这些属性并将传入的参数值应用到它们。
.MyComponent {
@include text(18px, 27px, 500);
}
// Compiles to
.MyComponent {
font-size: 18px;
line-height: 27px;
font-weight: 500;
}
虽然这是一个很好的演示,但这个特定的mixin有点局限性。它假设我们每次调用它时,都希望使用font-size
、line-height
和font-weight
属性。所以让我们使用Sass的if
语句来控制输出。
@mixin text($size, $lineHeight, $weight) {
// If the $size argument is not empty, then output the argument
@if $size != null {
font-size: $size;
}
// If the $lineHeight argument is not empty, then output the argument
@if $lineHeight != null {
line-height: $lineHeight;
}
// If the $weight argument is not empty, then output the argument
@if $weight != null {
font-weight: $weight;
}
}
.MyComponent {
@include text(12px, null, 300);
}
// Compiles to
.MyComponent {
font-size: 12px;
font-weight: 300;
}
这样好多了,但还不够完美。如果我尝试在不使用null
作为不想使用或提供的参数值的情况下使用mixin,Sass会生成错误。
.MyComponent {
@include text(12px, null); // left off $weight
}
// Compiles to an error:
// "Mixin text is missing argument $weight."
为了解决这个问题,我们可以为参数添加默认值,从而允许我们在函数调用中省略它们。所有可选参数都必须声明在任何必需参数之后。
// We define `null` as the default value for each argument
@mixin text($size: null, $lineHeight: null, $weight: null) {
@if $size != null {
font-size: $size;
}
@if $lineHeight != null {
line-height: $lineHeight;
}
@if $weight != null {
font-weight: $weight;
}
}
.MyComponent {
&-title {
@include text(16px, 19px, 600);
}
&-author {
@include text($weight: 800, $size: 12px);
}
}
// Compiles to
.MyComponent-title {
font-size: 16px;
line-height: 19px;
font-weight: 600;
}
.MyComponent-author {
font-size: 12px;
font-weight: 800;
}
默认参数值不仅使mixin更容易使用,而且我们还可以为参数命名并赋予它们可能常用的值。在上面第21行,mixin的参数顺序被打乱了,但由于也明确指定了参数值,因此mixin知道如何应用它们。
我每天都会使用一个特定的mixin:min-width
。我更喜欢先创建移动端优先的网站,或者说先考虑最小的视口。随着视口变宽,我定义断点来调整布局和代码。这就是我使用min-width
mixin的地方。
// Let's name this "min-width" and take a single argument we can
// use to define the viewport width in a media query.
@mixin min-width($threshold) {
// We're calling another function (scut-rem) to convert pixels to rem units.
// We'll cover that in the next section.
@media screen and (min-width: scut-rem($threshold)) {
@content;
}
}
.MyComponent {
display: block;
// Call the min-width mixin and pass 768 as the argument.
// min-width passes 768 and scut-rem converts the unit.
@include min-width(768) {
display: flex;
}
}
// Compiles to
.MyComponent {
display: block;
}
@media screen and (min-width: 48rem) {
.MyComponent {
display: flex;
}
}
这里有几个新想法。mixin包含一个名为@content
的嵌套函数。因此,在.MyComponent
类中,我们不再单独调用mixin,而是还调用了一段代码块,这段代码块将在生成的媒体查询中输出。生成的代码将在调用@content
的地方编译。这允许mixin处理@media
声明,并仍然接受该特定断点的自定义代码。
我还将mixin包含在.MyComponent
声明中。有些人提倡将所有响应式调用放在一个单独的样式表中,以减少样式表中@media
的写入次数。就我个人而言,我更喜欢将组件可能经历的所有变化和修改都与其组件的声明放在一起。这往往更容易跟踪正在发生的事情,并且如果出现问题,可以帮助调试组件,而不是筛选多个文件。
你注意到里面的scut-rem
函数了吗?这是一个来自名为Scut的Sass库的Sass函数,由David The Clark创建。让我们看看它是如何工作的。
开始使用函数
函数与mixin的不同之处在于,mixin旨在输出常见的属性组,而函数则根据返回新结果的参数修改属性。在本例中,scut-rem
接收像素值并将其转换为rem值。这允许我们以像素为单位思考,同时在幕后使用rem单位来避免所有计算。
在这个示例中,我简化了scut-rem
,因为它有一些额外的功能使用了循环和列表,这些内容超出了我们这里讨论的范围。让我们看看整个函数,然后一步一步地分解它。
// Simplified from the original source
$scut-rem-base: 16 !default;
@function scut-strip-unit ($num) {
@return $num / ($num * 0 + 1);
}
@function scut-rem ($pixels) {
@return scut-strip-unit($pixels) / $scut-rem-base * 1rem;
}
.MyComponent {
font-size: scut-rem(18px);
}
// Compiles to
.MyComponent {
font-size: 1.125rem;
}
首先要注意的是第2行的声明。在声明变量时使用了!default
,这告诉Sass将值设置为16,除非该变量已定义。因此,如果在样式表中较早地声明了具有不同值的变量,则这里不会覆盖它。
$fontSize: 16px;
$fontSize: 12px !default;
.MyComponent {
font-size: $fontSize;
}
// Compiles to
.MyComponent {
font-size: 16px;
}
接下来的部分是scut-strip-unit
。此函数接收px、rem、百分比或其他带后缀的值,并删除单位标签。调用scut-strip-unit(12px)
将返回12而不是12px。它是如何工作的?在Sass中,相同类型的单位除以另一个单位将去除单位并返回数字。
12px / 1px = 12
现在我们知道了这一点,让我们再次看看scut-strip-unit
函数。
@function scut-strip-unit ($num) {
@return $num / ($num * 0 + 1);
}
该函数接收一个单位并将其除以相同单位的1。因此,如果我们传入12px,则该函数将如下所示:@return 12px / (12px * 0 + 1)
。按照运算顺序,Sass首先计算括号内的内容。Sass巧妙地忽略了px
标签,计算表达式,并在完成后重新添加px
:12 * 0 + 1 = 1px
。现在的等式是12px / 1px
,我们知道它返回12。
这对scut-rem
为什么重要?让我们再看看它。
$scut-rem-base: 16 !default;
@function scut-rem ($pixels) {
@return scut-strip-unit($pixels) / $scut-rem-base * 1rem;
}
.MyComponent {
font-size: scut-rem(18px);
}
在第4行,scut-strip-unit
函数从参数中删除px
并返回18
。base变量等于16
,这将等式转换为:18 / 16 * 1rem
。请记住,Sass会忽略任何单位,直到等式的末尾,因此18 / 16 = 1.125
。该结果乘以1rem
得到1.125rem
。由于Scut从参数中删除了单位,因此我们可以使用无单位的值调用scut-rem
,例如scut-rem(18)
。
我不写很多函数,因为我尽量使我创建的东西尽可能简单。不过,能够使用像scut-rem
这样的东西进行一些复杂的转换还是很有帮助的。
占位符打乱的选择器顺序

小心扩展的内容
我尝试编写一些示例来演示为什么使用@extend
可能存在问题,但我使用它们的次数很少,无法创建任何像样的示例。当我第一次学习Sass时,我周围的队友已经经历了各种各样的挑战和磨难。我的朋友Jon Bebee写了一篇非常优秀的文章,介绍了@extend
如何让你陷入困境。这是一篇简短易读的文章,值得一读,所以我会稍后再读。
关于那些占位符……
Jon建议使用占位符作为他概述的问题的解决方案:占位符在与@extend
一起使用之前不会输出任何代码。
// % denotes an extended block
%item {
display: block;
width: 50%;
margin: 0 auto;
}
.MyComponent {
@extend %item;
color: blue;
}
// Compiles to
.MyComponent {
display: block;
width: 50%;
margin: 0 auto;
}
.MyComponent {
color: blue;
}

好的,等等。所以它输出.MyComponent
两次?为什么它没有简单地合并选择器?
这些是我第一次开始使用占位符(然后随后停止使用)时遇到的问题。线索是名称本身。占位符只是保存对声明它们的位置的引用。虽然mixin会将属性复制到使用它的位置,但占位符会将选择器复制到定义占位符的位置。因此,它会复制.MyComponent
选择器并将其放置在声明%item
的位置。考虑以下示例
%flexy {
display: flex;
}
.A {
color: blue;
}
.B {
@extend %flexy;
color: green;
}
.C {
@extend %flexy;
color: red;
}
// Compiles to
.B, .C {
display: flex;
}
.A {
color: blue;
}
.B {
color: green;
}
.C {
color: red;
}
即使B和C在样式表中更靠后声明,占位符也会将扩展的属性一直向上放置到它最初声明的位置。在这个示例中,这并不是什么大问题,因为它非常靠近使用它的源代码。但是,如果我们遵循前面介绍的7-1模式,那么占位符将定义在抽象文件夹中的一个部分中,它是第一个导入的文件之一。这会在扩展的预期位置和实际使用位置之间放置很多样式。这也很难维护,也很难调试。
Sass指南(当然)对占位符和扩展进行了很好的介绍,我建议阅读它。它不仅解释了扩展功能,而且最后还反对使用它。
关于
@extend
的优点和问题,意见似乎存在极大的分歧,以至于包括我在内的许多开发人员一直反对它,……
Sass还有许多其他功能我没有在这里介绍,例如循环和列表,但我老实说并没有像本文中介绍的功能那样依赖这些功能。浏览Sass文档,即使是为了看看这些功能的作用。你可能不会立即找到所有功能的用途,但可能会遇到这种情况,而掌握这些知识则非常宝贵。
如果我错过了什么或弄错了什么,请告诉我!我始终乐于接受新想法,并希望与您讨论!
进一步阅读
- Sass样式指南 – 关于保持Sass简洁和可维护性的想法和建议。
- CSS代码审查可能是什么样子 – 主要关注CSS,但也包括代码审查Sass文件的技巧。
- 定义和处理技术债务 – 我们在这篇文章中讨论了很多关于DRY方法和效率的内容。这篇文章讨论了冗长或低效代码的各种后果。
- CSS预处理器中的循环 – 我们在这里没有介绍循环,但Miriam Suzanne在这篇文章中深入探讨了循环。
- Sass代码片段 – 一系列实用的Sass mixin和其他Sass模式。
这些文章总是很有帮助,可以帮助我们重新学习一些遗忘的基础知识,并提醒我们最佳实践。需要学习的东西太多了,以至于你可能会忽略简单的事情。感谢你的总结,我肯定会重新审视我的 & 符号用法和变量作用域。
谢谢!重新回顾基础知识总是很有帮助的。& 符号是我最喜欢的工具之一。
这是我最喜欢的 SASS 技巧
这允许你多次导入同一个文件,但在最终构建中只获得一份样式;类似于 webpack。这具有以下优势:
– 显然,你可以获得更小的 CSS 文件大小,但你还可以提高样式的性能(没有冗余规则)并且使检查器内容更易读。
– 你可以随意在任何其他样式依赖它的任何地方 @import 该文件,这对于自文档化以及稳定性都非常有益。
这很酷!你使用 7-1 模式吗?你的主样式表文件是什么样子的?
我们没有;我们的 SCSS 架构与我们的 React 组件结构平行——每个 .react.js 文件对应一个 SCSS 文件。我们在顶部确实有一个包含少量全局样式的文件,但几乎所有内容都存在于这些组件中。除了方便查找与您正在查看的标记相关的样式外,这还使导入变得简单。如果您的 JS 文件导入另一个组件,则相应的 SCSS 文件也需要导入该组件的 SCSS 文件。入口点仅导入“应用程序”级组件,其他所有内容都处理其自身的依赖项。
不错!React 是我通过 Wes Bos 的课程积极学习的东西。
喜欢!一些很棒的 Sass 技巧的绝佳方法和执行。
我必须表示,我完全不同意在 SCSS 中建议使用 & 符号的部分内容。我个人认为,在部分类名中使用 & 符号会使代码难以维护——除非你是编写它的人,并且是最近编写的——。
使用您帖子中的示例代码段(因为它展示了以两种不同方式使用 & 符号)
如果我在 DOM 中看到一个元素应用了
.MyComponent--title
类,我希望我可以复制它,然后 grep 代码库以找到该类的确切定义/s。使用 & 符号会破坏这种能力,这意味着开发人员必须已经熟悉代码库才能维护/扩展它(源映射绝对可以提供帮助,但假设我们没有它们)。当我在 CR 中看到以这种方式使用 & 符号时,我总是要求开发人员更改它。现在键入的几个额外字符将节省他们,或者更重要的是未来的开发人员,在尝试查找要编辑的源代码时节省大量时间。查找和替换存在于每个 IDE/文本编辑器中,因此将来重构并不难,如果MyComponent
变成MyNewComponent
但是,该代码段中 & 符号的第二种用法
.MyComponent--xmasTheme &
是完全可以接受的——尽管这不是我个人希望一直使用的。我对此感到满意的原因是,你不太可能在代码库中搜索生成的编译 CSS.MyComponent .MyComponent--xmasTheme
。我希望大多数人只会获取一个类名并使用它来 grep 代码库。很想听听你的想法..
双方肯定都有强烈的想法,尤其是在可维护性方面:https://css-tricks.cn/sass-selector-combining/
双方肯定需要考虑一些因素。对于我和我的团队来说,我们从未遇到过任何问题。如果我们采用像这样的架构,我们会确保创建的任何组件都有自己的文件。我们不是 grep 类名,而是调出该特定组件的文件。我们要求团队在创建 1 个组件 -> 1 个文件中保持勤奋,并且实际上没有遇到任何问题。
由于我们还努力使我们的文件更简单并总体上包含更少的代码,因此我们可以打开一个文件并快速扫描以找到我们正在寻找的内容。
再说一次,这两种方法都有利弊,您只需要选择最适合您和您团队的方法即可。
如果您使用 ITCSS* 方式组织项目文件并遵循 harry roberts(CSS 奇才)提出的 CSS 命名空间约定以及他对 BEM 的建议,则这不是问题。如果您在 DOM 中看到“c-MyComponent–title”,则转到您的 CSS 文件夹组件文件夹,即“sass>components/_MyComponent.scss”,这很容易。您永远不需要在项目中搜索此文本,因为对它的任何引用都应该只存在于该文件中。
因此,这里的“c-”表示“组件”,ITCSS 有一个组件层,对应于一个“组件”文件夹(最初的 ITCSS 建议是在文件名前面加上层,例如 component-myComponent.scss,并将所有部分放在一个文件中,但在我的公司,我告诉他们将 ITCSS 层放在文件夹中)。
我们的约定是将文件名命名为与 BEM 中的“块”类相同。正如我所说,找到您想要的东西非常容易,您甚至不需要源映射来查找您的内容。ITCSS、BEM 和命名空间的组合原则在很大程度上消除了我认为您正在谈论的问题和级联问题。您只需阅读 DOM 即可准确知道您的 CSS 存储在哪里。去看看吧,它会改变你的生活!
*仅适用于单体,在样式可能来自各处的分布式组件环境中效果不佳。这就是 CSS-in-JS 很好的地方。
关于编写“&-title”之类内容的警告:它使代码难以维护,因为您不能只搜索“component1-title”之类的内容。
如果您使用源映射,问题就不复存在了,是吗?
SASS 确实很棒。但我讨厌整个预编译器世界的是构建工具。Webpack、Gulp、等等设置起来太复杂了。变得太难了。
作为一名设计师,我希望有一些 GUI 来设置所有这些构建工具。每个开发人员都像“那很容易,只要阅读文档”。但为此,我首先必须学习埃及学才能理解所有这些东西。别提调试了。它在哪里失败了?在 webpack 中?在 Gulp 中?在 NPM 中?没人知道。而我想要的只是编写 Sass 并缩小它。
如果我在这个网站上有什么愿望,那就是“为白痴提供的构建工具指南”。
SASS 的一个优点是,你只需使用标准命令行工具,无需配置文件即可完成所有操作——导入、转译,甚至缩小。不需要 gulp、webpack 或任何其他东西。不过,您绝对是对的,Web 构建工具领域过于复杂且脆弱,不利于自身发展。
但是确实有这样的工具:)……查看 CodeKit!
我过去曾使用 & 符号嵌套 BAMS 样式选择器,但最近我已不再使用它。我发现其他开发人员在 DOM 中找到他们搜索的选择器时很难找到它们。例如,他们搜索
.Component1-title
,但找不到,因为它在 SCSS 文件中被拆分了。也许这只是所有团队都在同一个框架上工作的大型团队才会遇到的问题?我很想知道是否还有其他人遇到过这种情况?
在我的开发环境中,大约有 160 名开发人员,其中大约 40 名是前端开发人员,如果我没记错的话。我们都分成 2 到 5 人的小组,负责不同的业务部门。我们大多数前端开发人员都以这种方式编写代码,并且在将新开发人员纳入流程时没有遇到任何问题。关键因素之一是选择一种样式并在特定项目中坚持使用它。如果您要进行像我上面描述的那样的 & 符号嵌套,则应在整个项目中执行此操作。如果您要写出组件名称,则应始终写出它们。
好文章。谢谢 :)
很棒的帖子。我经常使用
&__
和&--
技巧来实现 BEM,我喜欢事物井井有条的样子。我发现占位符和扩展确实有很好的用途。我最近从软件开发转向了代理商 Web 设计/开发,在这里我们做了很多与第三方系统(票务)的集成,你无法控制 HTML,但你仍然需要重新调整外观以匹配主要设计。并且您可能需要在同一个项目上执行两个或三个不同的 3PI,并且需要非常快速的周转时间。因此,我在主要开发期间将关键品牌元素编写为模型,然后像这样将它们 @extend 到 3PI css 表格/s 中
等等。然后在我们的主要内容中,它只是
这意味着我确保编写这些样式独立于我们的特定 HTML,并且可以很容易地将其放到任何我需要的元素上,并且我不需要进行大量转换到我正在处理的任何疯狂 HTML。当主要内容发生变化时,它会获得大部分设计时间,它会自动更新所有这些 3PI 表格,因此我们不会出现偏差。例如,如果它是某种版本的 Bootstrap,它通常是。它只是
(
#{$s}
是我在主题中设置的特殊性提升,因为你无法始终控制你的 CSS 将显示在哪里。或者它与什么冲突。)而且,如果你没有框架或组件系统或任何东西,它只是别人的未记录的前端,充满了他们无法更新的几十年来的随机积累(因为所有集成!),你可以通过定义模型来为它带来一些秩序,比如这是一个号召性用语,这是辅助或页面导航,等等。然后你可以逐页浏览,并将这些随机的 HTML 大致分类到这些组中,并使用 extends 快速实现(某种)一致的外观,而不会出现巨大的膨胀(例如,如果无法访问 gzip)。
所以,我认为 extends 在这些前线确实有一些很好的用途,尽管我从未在软件世界中使用过它们,并且我同意,当你构建整个系统时,有更好的方法。
“这很棒,因为我们可以利用 & 符号来编写组件名称一次,并在整个过程中简单地引用组件名称。”
但是 IDE 中的搜索完全被破坏了。如何使用如下代码在整个项目中搜索“.MyComponent-title”?
.MyComponent {
&-title {}
}
拜托大家,不要在 SCSS 中这样做。我经常处理其他人的代码库,其中应用了这种技术,而且我大部分时间都完全迷茫。我的 IDE 中的自动完成和跳转到源代码功能都坏了。
仅仅因为你可以在 Sass 中做一些聪明的事情,并不意味着你应该这样做。保持接近 CSS - 浏览器(和人们)理解的东西。查看并学习级联。使用 Sass 变量来保持一致性。较短的源代码几乎不是一个论点,而且这也不会为你节省任何时间 - 它会让下一个开发者花费更多的时间。
Harry Roberts 在这里提出了很好的论点:https://csswizardry.com/2017/02/code-smells-in-css-revisited/:“我更有可能查找一个类,而不是重命名它”。
@Joris 嗯……你链接的文章提倡在整个过程中使用类似 BEM 的语法。这正是我在这篇文章中提倡的。
编辑
或者你是反对 & 选择器吗?
或者你是反对 & 选择器吗?
是的,& 选择器。
不知道它有这么多用途,我通常只在 :hover 和事件相关的东西中使用它来保持代码整洁。但你的文章让我大开眼界,有很多可能性。感谢分享