函数式 CSS
在Wealthfront我们是一个函数式编程的超级粉丝。强调不变性和函数式风格意味着更少的“意外”(surprises),因为副作用是有限的或不存在的。我们能将独立的组件迅速构建出大型系统,通过组合的方式组合组件。函数式编程原则直接应用在大多数语言中,即使他们不是被定义为函数式。同样适用非函数式的css。让我们看下我们最喜欢的(和最讨厌的)一些特性在样式语言中。
- 一切都是全局作用域。
- 一切都是可变的。
- 优先级的计算,基于一些有趣的规则。
因此让我们讨论下我们能做什么。Wealthfront的 CSS(实际是 SCSS)风格指南概述一些经验法则,让我们在CSS中获得函数式编程范式的效益。确切的说,指南通过限制副作用减少意外,提倡组合使我们的样式表更具伸缩性。在本文中,我将介绍一些我们的样式指南中的主要的规则。
引入作用域
大部分语言中,变量定义被限制在它们的作用域内。在Javascript中,变量的作用域是他们所在的函数,而其他语言中,如Java变量具有块级作用域。我在我的作用域里定义的变量不能被其他人在我的作用域外重定义或修改。
作用域是防御式编程和降低副作用的重要部分。如果您的规则、函数或变量只存在于一个受限的作用域,那么你可以放心没人会更改它,无论是有意还是无意的。
CSS没有作用域。样式定义时可能意外重写其他规则,而且无法保证你挑选的样式规则名称没被其他人使用。它可能在完全不同的文件里,并且是深度嵌套的选择符。假如你问你的样式规则选择了一个相同的名字,则意外重写其他人规则的概率骤然升高。让我们思考下面的例子:
/* profile.css */
.error { color: orange; }
.success { color: blue; }
/* signup.css */
.error { color: red; }
.success { color: green; }
假如我们在我们的HTML中引入上面的CSS文件,我们无意中用其中一个样式重写(覆盖)了另一个。随着网站的增长,这种状况变得越来越复杂和普遍。
那么我们如何在CSS中引入作用域呢?
在CSS中模拟更细粒度的作用域的安全做法是用命名约定。在这种情况下,我们为我们所有的样式规则设置命名空间通过添加一个前缀。前缀命名空间规则不是一个新的理念,但重要的是我们知道我们为什么那样做。在前缀的样式中我们创造了我们的“作用域”,你可以说我们的css是在“prefix”作用域内——我们的样式只在设置了前缀的规则中存在。
让我们用前缀符尝试修改前面的例子:
/* profile.css */
.profile-error { color: orange; }
.profile-success { color: blue; }
/* signup.css */
.signup-error { color: red; }
.signup-success { color: green; }
加前缀允许我们封装我们的规则,保护它们免受修改。有了这些附加的前缀,我们的规则不再冲突。我们使他们免受副作用,通过声明我们的规则在命名空间作用域中。
减小依赖,增大可重用性
通过使用复杂的选择符来保持我们的标记整洁和无类是很容易的。我们都见过像下面这样的CSS:
.whitepaper-link {
font-weight: bold;
font-size:12px;
}
.main-nav .whitepaper-link {
font-size:16px;
}
.main-footer .whitepaper-link {
font-size:9px;
}
但如果我们想在其他地方有小号的.whitepaper-link呢?上面这样的嵌套选择符我们在我们的样式中强制了DOM结构(样式规则和DOM结构紧紧耦合在一起)。这就是说”你只能在main-footer中有一个小的whitepaper链接“。在CSS规则中强制结构阻止我们重用样式,并且将我们数据的表现和它在结构中的表现混合在一起(结构和表现紧耦合)。当我们通过嵌套的选择符强制结构时,我们在它们间创造了依赖。在软件工程的任何区域管理依赖都是令人头痛和容易出错的。我们应该避免。
代替强制结构,让我们像下面这样定义它:
.whitepaper-link {
font-weight: bold;
font-size:12px;
}
.whitepaper-link-large {
font-size:16px;
}
.whitepaper-link-small {
font-size:9px;
}
我们的标记能添加 .whitepaper-link 和 .whitepaper-link-small 类到页脚元素来实现和旧版中一样的效果。现在我们能复用 “small”样式到站点中的任何其他元素,无论它是否在footer元素内。我们在这真正见到的是组合的魔力,我们将在一分钟后讨论。
避免突变性
在CSS中重写样式规则并非是罕见的(恰恰相反)。例如,你可能想让一个错误消息有不同的效果如果它在侧边栏里。
/* errors.css.scss */
.error { color: red; }
.sidebar .error { border:1px solid red; }
这是意大利面条式代码的东西。这不是与一群工程师使用全局变量类似。一些工程师在他们的代码中将重定义变量(样式),然而其他人仍然期望它(变量)保持原来的定义。这 .error 样式变得不再安全,无法知道它在给定的上下文中的确切行为。样式规则变得充满意外,然而我们讨厌意外。
解决方案是从不重写一个已经定义的样式规则。如果你把规则当成不可更改的——那意味着,它们是一成不变的,定义之后也永远无法改变它们——你可以避免许多由全局变量和不稳定变量引起的问题。
我们可以通过组合实现这些,无论我们通过元素的类属性或通过Sass的@extend 指令。
组合是你的福音
让我们看下我们如何用我们上面描述的来完成例子。
/* errors.css.scss */
.error { color: red; }
.sidebar-error { border:1px solid red; }
\<!-- example.html -->
\<div class="error sidebar-error">Oh no!</div>
我们没有重定义 .error 规则,而是给我们的 error div增加一个新的规则来增强它。我们的 error div 的表现将是 .error 和 .sidebar-error的组合。
这仍然让人有点迷惑,我们没有重写 .error 规则自己,但我们重写他的一个属性。如果你正在使用 Sass,更富有表现力的在你的样式中用SCSS方式定义组合是通过@extend指令。
/*errors.css.scss*/
.error { color: red; }
.sidebar-error {
@extend .error;
border:1px solid red;
}
example.html
<!-- example.html -->
<div class="sidebar-error">Oh no!</div>
现在我们的标记保持苗条,并且没有给人错误的印象,它看起来应该像 .error。任何浏览错误样式表的开发者将看到 .slidebar-error是 .error 外加一个边扩展。他们能自信的使用 .error 因为它从没被重定义,并且我们仍将有我们自定义的 .sidebar-error 表现。
FCSS
Wealthfront有一些更多的规则,我们在本文中没有讨论。但他们都融入到整个我们讨论过的指导原则中。例如,我们避免元素和过多伪类及伪对象选择符,因为他们和DOM结构紧耦合,并且我们更喜欢类选择符替代id选择符来提高可重用性(ID 选择符被用来非常特殊的,单个元素重写或样式)。
在此强调:
- 通过前缀为你的类增加命名空间,增加伪作用域和减少意外。
- 不要通过多次定义重写样式中的规则,而是用组合。
- 不要用嵌套,元素选择符或过度的伪类及伪对象选择符——他们将你的CSS和DOM结构仅仅绑定在一起。
- 每一个团队需要自己的样式指南在缺少约束的语言中强制约束。如果运气好,我们的“函数式”方法将带给你一些灵感。
注:本文为翻译文章,原文为 Functional CSS (FCSS)。
更多建议: