作为一名拥有近 15 年经验的前端开发人员,我亲眼目睹了 Web 开发的演变。对我来说,从通过 FTP 上传修改后的文件的时代(是的,GitHub 15 年前就推出了,但我直到 2011 年才发现它)到响应式界面、UI 库和直接生成网站的现代时代已经走过了很长一段路。图玛。
然而,我仍然遇到使用嵌套样式的项目,例如:
.some-class ul li div a { /* some style */ }
或者,
#nav .nav-link svg { /* some style */ }
这可能看起来令人震惊,但这种编码实践无处不在,从价值数百万美元、快速增长的项目到不起眼的初创公司。
让我们深入研究一下为什么这种方法会带来问题。
深度嵌套的样式通常会导致样式冲突,尤其是当您有一个大项目时。 CSS 作为级联样式表,根据元素的特殊性向下级联并应用于元素。由于其特殊性,深度嵌套的样式可能会无意中覆盖其他样式。
考虑这个例子:
.some-class ul li div a { color: red; } ... .some-class a { color: blue; }
您可能期望.some-class
中的所有链接都是蓝色的。然而,由于第一条规则具有更大的特异性,任何嵌套在ul > li > div
中的链接都将是红色的,而不是蓝色的。这可能会导致意外的视觉不一致并浪费大量调试时间。
理解 CSS 中的特殊性(或样式的“权重”)概念对于理解为什么深度嵌套可能很麻烦至关重要。如果多个规则竞争单个元素,特异性决定了应用哪个 CSS 规则。它是根据选择器的类型和数量计算的。
特异性是根据四类加权系统计算的:
在这里,考虑一下规则:
body #content .data img:hover { /* some style */ }
特异性为0 1 2 2 。这是一个 ID ( #content )、两个类( .data和:hover )和两个元素( body和img )。
现在,考虑一下规则:
#nav .nav-link svg { /* some style */ }
这里的特异性是0 1 1 1 。这是一个 ID ( #nav
)、一个类 ( .nav-link
) 和一个元素 ( svg
)。
特异性并不像传统的十进制数那样在“结转”系统上运行。例如,特异性为0 1 0 11的选择器不等于特异性为0 1 1 1 ,即使在十进制系统中, 11和1+1是等效的。
最后,通用选择器 ( *
)、组合器 ( +
、 >
、 ~
、 ' ') 和否定伪类 ( :not()
) 对特异性没有影响。然而,在:not()
参数内,选择器照常计数。
对于视觉学习者,我推荐这个有关 CSS Specificity 的视频。
了解 CSS 特异性及其计算方式可以让您编写更好、更可预测的 CSS,并在样式未按预期应用时调试问题。
!important
规则和特殊性有时,开发人员在遇到 CSS 特异性冲突的困难时会求助于使用!important
规则。该规则使 CSS 属性变得非常具体,这意味着它将覆盖几乎所有其他声明。
例如:
#nav .nav-link svg { color: blue; } .nav-link svg { color: red !important; }
尽管第一条规则由于 ID 选择器而具有更高的特异性,但由于第二条规则中的!important
, svg
的颜色将为红色。
虽然!important
可以在解决特异性问题时快速解决,但不建议广泛使用它。过度使用会影响可维护性、可预测性和性能。在较大的项目中,过度使用!important
通常表明在管理 CSS 特异性方面存在困难。与其诉诸!important
,通常更好的做法是投入时间重构 CSS 并减少过于具体的选择器的使用。
您现在就可以检查您的产品🙂。我检查了我的:
虽然!important
可能是一个诱人的快速解决方案;这就像用大锤敲开坚果一样。更易于维护的方法是让您的选择器尽可能简单、扁平化,这使得您的 CSS 将来更容易理解、管理和扩展。请记住,赢得一场!important
战争的最佳方法就是从一开始就不要发动战争。
深度嵌套样式的另一个问题是对浏览器渲染的性能影响。当浏览器将样式应用于元素时,它会从右到左遍历 DOM,从键选择器(在我们的示例中为a
和svg
)开始,并在祖先中移动,直到找到匹配项或到达顶部。样式嵌套越多,遍历所需的时间就越长,可能会影响性能并减慢大型项目中的页面加载时间。
当您指定 CSS 规则时,例如:
.some-class ul li a { /* some style */ }
您可以从树的底部(从a
标记)开始可视化此规则,并在树中向上(通过li
、 ul
和.some-class
)。
浏览器将首先查找所有(我的意思是所有) a
元素,然后它将检查这些a
标签是否在li
元素内。之后,它将检查这些li
元素是否在ul
内部,最后,它将检查这些ul
是否在类为.some-class
的元素内部。
这就是浏览器读取 CSS 选择器的方式,也是复杂选择器会导致页面渲染速度变慢的原因。浏览器必须对每个元素进行多次检查,看看它是否符合指定的规则。规则越深,浏览器必须进行的检查越多,这可能会影响性能。
CSS 模块允许您在各个模块中编写 CSS,这些模块的范围仅限于您正在使用的组件的本地范围。这意味着 CSS 模块中的样式仅适用于该特定模块,不会泄漏或影响页面上的其他元素。
让我们探讨 CSS 模块如何使用散列类名来确保样式封装。当您使用 CSS 模块时,您在 CSS 文件中定义的类名将在编译时进行哈希处理。此哈希创建与您的组件相对应的唯一类名称。让我们看一个例子:
假设您有一个如下定义的 CSS 模块:
/* Button.module.css */ .button { color: white; background-color: blue; }
您可以像这样在组件中使用它(我更喜欢将样式对象导入为s
而不是styles
- 这是节省打字时间并提高编码效率的快速提示):
import React from 'react'; import s from './Button.module.css'; const Button = () => { return ( <button className={s.button}>Click me</button> ); }; export default Button;
编译应用程序时,呈现的 HTML 可能如下所示:
<button class="Button_button__3FQ9Z">Click me</button>
在本例中, Button_button__3FQ9Z
是从 CSS 模块生成的散列类名称。请注意,哈希的确切结构和长度可能会根据您的项目配置而有所不同。
这个唯一的类名确保您在Button.module.css
中定义的样式仅适用于该按钮,不会影响应用程序中的任何其他元素。它还确保没有其他样式可以影响此按钮,除非它们明确针对散列类名。这种样式封装是 CSS 模块的主要优点之一。
处理CSS的另一种流行方法是使用CSS-in-JS库,例如styled-components或emotion 。这些库允许您直接在 JavaScript 中编写 CSS,这有几个好处:
以下是如何在 React 应用程序中使用样式组件的示例:
import React from 'react'; import styled from 'styled-components'; const Button = styled.button` color: white; background-color: ${(props) => props.primary ? 'blue' : 'gray'}; `; const App = () => { return ( <div> <Button primary>Primary Button</Button> <Button>Secondary Button</Button> </div> ); }; export default App;
在此示例中, Button
组件具有根据其primary
属性而变化的动态样式。
如果您没有使用支持 CSS 模块或类似框架的 JavaScript 框架,您仍然可以使用BEM(块、元素、修饰符)等命名方法有效地管理 CSS。
BEM 代表“块元素修改器”,它是一种帮助您在 CSS 中创建可重用组件和代码共享的方法。以下是您如何使用 BEM 构建 CSS:
/* Block */ .top-menu { } /* Element */ .top-menu__item { } /* Modifier */ .top-menu__item_active { }
在 BEM 中,“块”是本身有意义的独立实体,“元素”是块的一部分,没有独立含义,并且在语义上与其块相关联,而“修饰符”是用于更改外观或行为的块或元素。
使用 BEM 等一致的方法可以使您的 CSS 更易于理解和维护,尤其是在大型项目中。
在大型项目中管理 CSS 的方法有多种,从 CSS 模块和 CSS-in-JS 库到 BEM 等命名方法。关键是找到一种适合您的团队和项目的方法并始终如一地应用它。请记住,编写 CSS 不仅涉及编写高效且高性能的代码,还涉及编写易于理解和维护的代码。
快乐编码!