paint-brush
已经是 2023 年了,但我们仍然需要讨论 CSS 中的嵌套样式经过@lastcallofsummer
3,669 讀數
3,669 讀數

已经是 2023 年了,但我们仍然需要讨论 CSS 中的嵌套样式

经过 Olga Stogova8m2023/07/17
Read on Terminal Reader

太長; 讀書

深度嵌套的样式通常会导致样式冲突,尤其是当您有一个大项目时。这可能会导致意外的视觉不一致并浪费大量时间。 CSS 中的特殊性(或样式的“权重”)概念对于理解为什么深度嵌套会很麻烦至关重要。
featured image - 已经是 2023 年了,但我们仍然需要讨论 CSS 中的嵌套样式
Olga Stogova HackerNoon profile picture
0-item

即使“CSS 样式”请求的随机图片也包含嵌套样式。


作为一名拥有近 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 规则。它是根据选择器的类型和数量计算的。


特异性是根据四类加权系统计算的:


  1. 内联样式
  2. ID
  3. 类、属性和伪类
  4. 元素和伪元素


在这里,考虑一下规则:


 body #content .data img:hover { /* some style */ }


特异性为0 1 2 2 。这是一个 ID ( #content )、两个类( .data:hover )和两个元素( bodyimg )。


现在,考虑一下规则:


 #nav .nav-link svg { /* some style */ }


这里的特异性是0 1 1 1 。这是一个 ID ( #nav )、一个类 ( .nav-link ) 和一个元素 ( svg )。


特异性并不像传统的十进制数那样在“结转”系统上运行。例如,特异性为0 1 0 11的选择器不等于特异性为0 1 1 1 ,即使在十进制系统中, 111+1是等效的。


最后,通用选择器 ( * )、组合器 ( +>~ 、 ' ') 和否定伪类 ( :not() ) 对特异性没有影响。然而,在:not()参数内,选择器照常计数。


对于视觉学习者,我推荐这个有关 CSS Specificity 的视频


了解 CSS 特异性及其计算方式可以让您编写更好、更可预测的 CSS,并在样式未按预期应用时调试问题。

!important规则和特殊性

有时,开发人员在遇到 CSS 特异性冲突的困难时会求助于使用!important规则。该规则使 CSS 属性变得非常具体,这意味着它将覆盖几乎所有其他声明。


例如:


 #nav .nav-link svg { color: blue; } .nav-link svg { color: red !important; }


尽管第一条规则由于 ID 选择器而具有更高的特异性,但由于第二条规则中的!importantsvg的颜色将为红色。

过度使用的风险!重要

虽然!important可以在解决特异性问题时快速解决,但不建议广泛使用它。过度使用会影响可维护性、可预测性和性能。在较大的项目中,过度使用!important通常表明在管理 CSS 特异性方面存在困难。与其诉诸!important ,通常更好的做法是投入时间重构 CSS 并减少过于具体的选择器的使用。


您现在就可以检查您的产品🙂。我检查了我的:


55 个文件中的 111 个结果,对于一个大产品来说已经不错了!


虽然!important可能是一个诱人的快速解决方案;这就像用大锤敲开坚果一样。更易于维护的方法是让您的选择器尽可能简单、扁平化,这使得您的 CSS 将来更容易理解、管理和扩展。请记住,赢得一场!important战争的最佳方法就是从一开始就不要发动战争。

遍历 CSS 树

深度嵌套样式的另一个问题是对浏览器渲染的性能影响。当浏览器将样式应用于元素时,它会从右到左遍历 DOM,从键选择器(在我们的示例中为asvg )开始,并在祖先中移动,直到找到匹配项或到达顶部。样式嵌套越多,遍历所需的时间就越长,可能会影响性能并减慢大型项目中的页面加载时间。


当您指定 CSS 规则时,例如:


 .some-class ul li a { /* some style */ }


您可以从树的底部(从a标记)开始可视化此规则,并在树中向上(通过liul.some-class )。



只是 CSS 对象模型的一小部分



浏览器将首先查找所有(我的意思是所有) a元素,然后它将检查这些a标签是否在li元素内。之后,它将检查这些li元素是否在ul内部,最后,它将检查这些ul是否在类为.some-class的元素内部。


这就是浏览器读取 CSS 选择器的方式,也是复杂选择器会导致页面渲染速度变慢的原因。浏览器必须对每个元素进行多次检查,看看它是否符合指定的规则。规则越深,浏览器必须进行的检查越多,这可能会影响性能。

在大型项目中管理 CSS 的更好实践

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-in-JS 库

处理CSS的另一种流行方法是使用CSS-in-JS库,例如styled-componentsemotion 。这些库允许您直接在 JavaScript 中编写 CSS,这有几个好处:


  1. 作用域样式:与 CSS 模块类似,样式的作用域是定义它们的组件。
  2. 动态样式:根据组件中的 props 或状态创建动态样式很容易。
  3. 改进的开发人员体验:您可以直接在样式中使用 JavaScript 逻辑,并且与组件相关的所有内容都位于一个位置,这可以使您的代码更易于理解和使用。


以下是如何在 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 不仅涉及编写高效且高性能的代码,还涉及编写易于理解和维护的代码。


快乐编码!