为什么我在css里使用功能类优先
阅读原文时间:2023年07月10日阅读:1

前言

我想在我们开始的学CSS语法的时候,都是从以下的流程开始的:

1、写一个CSS类选择器:

.my-class {
}

2、往选择器里填充CSS语法:

.my-class {
    display  flex;
    flex-direction  row
}

3、在 HTML的 class 属性中写上选择器名:

div class=”my-class”div

以此来将CSS的效果应用到HTML上。

在很长的一段时间里,我也一直都是这么写的。但是慢慢地,我感觉到越来越别扭,却说不出是哪里不对。到后来我接触到了像bootstrap之类的UI库,这种UI库最大的特点就是将每一个CSS类都写成了一个CSS选择器的方式,比如:.text-center 里只有一个CSS语法——text-align center; 起初我以为这种啰啰嗦嗦的方式会让我写起来非常麻烦,但我却发现这种方式比起以往的写法,给我带来了相当大的便利,我也想明白了以往的CSS写法别扭在什么地方。我将在本文中描述我的感受。

语义类CSS和功能类CSS

语义类和功能类两者其实是CSS的两种写法。

我在前言中说到的,将CSS语法写在CSS选择器中,再将选择器名写在HTML的class属性中,这种写法就是语义类,也有叫做内嵌类的。而功能类就是像bootstrap之类的UI库的写法,特点是通过某种方式直接在HTML的class类名中写上代表CSS的某个语法的名字,来实现少数的CSS效果。比如

div class=”text-center”
    这里应用的CSS类选择器text-center,其实际内容只有一个CSS语法text-align center;
div

这两种方法,可能也有别的名字,但这不重要,重要的是我们知道它们代表什么,在本文中,就叫语义类和功能类吧。

我们可以很明显地看到两种写法的迥然不同之处。语义化的写法提倡将一系列的CSS语法写在选择器里,再给选择器取一个合适的名字,应用到HTML上,以达到语义化的效果。好处是可以根据类名来达到重用(重点),如:有一个CSS类,叫 .btn,我在里面定义按钮的样式效果,之后我就可以给所有的 button标签应用上该 .btn类,来达到同样的按钮效果。

功能类的语法则是完全摒弃了CSS类名的语义,在这里,CSS的类名只用来表示实现的功能,而不表示含义。比如CSS类名 .text-center,这个CSS类名如果只从名字上来看是看不出任何语义的,但是它可以一目了然地看出该CSS类的实现是:text-align center; 达到居中效果,且也只有这一个效果。其特点在于,CSS类名就是其实现。那么,对于每一个CSS的实现,就必须有一个CSS类名来对应,如此一来,CSS庞大的实现语句也一定会造就庞大的CSS功能类,人工是不可能在项目中先写好需要用到的CSS类名再去开发的。所以,引用第三方的UI库就是使用功能类的先决条件。

如果是从上面两者的介绍来看,语义类的优点是很明显的:类名简单易懂,可重用,规范容易统一。再看功能类,似乎看不出优点,而且让人觉得:写起来啰里啰唆,对于复杂的效果类名很长,不同元素的相同效果无法重用。功能类简直是看不到什么好处。

既然两者的优劣这么明显,那我又是为何从语义类优先的写法转变到功能类优先的写法呢?

为何从语义类优先转变到功能类优先

当我大量的使用语义类开发的时候,我发现我很多地方都用上了style语法,就是直接修改元素的style属性:

div class=”text-center”
    语义类
div
div class=”text-center” style=”text-align text;”
    语义类并且,使用了style语法。
div

因为当我为一个元素编写样式的时候,是无法断定这个样式是否能够完美的应用在其他需要的元素上,比如像div、span、main等其他布局用途的元素。按钮等功能元素不在此列。

原因之一是使用HTML语法来编写同一个页面可以有多种方式,不同方式所能搭配的CSS语句不同。可以这么说,只要不是两个完全一摸一样的页面,其样式上就会有差别。页面布局的表现,是建立在多个元素的搭配下的。比如我要编写一个容器的左右布局,至少需要三个元素,其中一个元素表示容器,一个元素表示容器内的左侧内容,一个元素表示容器内的右侧内容。更复杂的布局,需要的元素组合也就更多,当元素内元素一多,为了容器内多个元素的统一操作,也就需要另一个容器将这多个需要统一操作的元素包裹起来,即容器内的容器元素。

CSS只能够应用在元素上,使应用的元素的样式发生变化,flex,grid语句也能够使元素内部的结构发生变化。不过CSS仍然只能美化它应用的元素,不能美化它的自己的子级元素。所以,如果要让CSS达到美化一整个容器的效果,就需要让CSS应用到每一个容器内元素上。打个比方,有如下布局:

div class=”container”
    div class=”left-side”
        左侧内容
div
    div class=”right-side”
        右侧内容
div
div

如果我只实现了container类的CSS美化效果,只会美化container。为了使container内部的left-side和right-side也美化,我也就需要单独美化这两者。然而,容器内左侧和右侧的美化效果如何,完全取决于它们的容器是什么。如果它们的容器是一个导航栏,那么容器内左侧和右侧要怎么美化;如果容器是个列表框,那么容器内左侧和右侧要怎么美化。由此可见,单单一个container类是无法重用的,很自然的,我们必须细分出导航栏的nav类和列表框的list类。我们的container变成了两个截然不同的CSS类。

div class=”nav”
    ……
div
div class=”list”
    ……
div

导航栏在大多数项目中样式都比较统一,如果我们分析导航栏的样式,就会知道,导航栏的一系列CSS类只能应用在导航栏中,完全无法应用到其他类型的布局中。这也是由于将CSS应用到容器和它的各个子元素中决定的,容器中的子元素,使用什么美化语句,需要视容器元素而定。这样一来,如果我要将nav应用到列表框,就需要将导航栏容器以及它的子级CSS类都逐一应用到列表框里。但是这种逐一应用无法实现的原因在于,两者的元素布局不同,只要两者的容器和子级不是完全一样,就无法逐一应用CSS类。

CSS语义类名只能完全服务于它的元素。

按照这个理论,我们也就能解释为什么按钮可以做到CSS类名重用,像bootstrap中的btn类名应用到所有按钮中,因为我们通常只使用按钮这个元素本身,不会给他再嵌套进其他元素。同理,像A标签也一样。但是如果我们尝试给按钮或者A标签中添加其他子元素,就能够看到它们变得怪异。

在我认识到这一点之前,我仍然在大量使用语义类CSS。很快,在重用CSS类到其他元素的时候我就遇到了怪异的情况:元素的美化效果不完全。这个时候为了调整样式,要么修改应用的CSS类,但这样一来,原先的元素的效果就会遭到破坏。要么多写一个CSS类,单独用来调整元素的效果,但是CSS语句的优先级是不可控的。所以为了不影响原先元素,为了可控,为了方便,我就直接写成了style语句。这种方式让HTML变得奇丑无比——页面上时不时就会冒出来style的写法。

很常见的一种情况就是一个元素,只需要一句CSS语句。我经常遇到一个元素我只需要调整它内部的文本位置,靠左靠右还是居中,调整一些内边距外边距。这种细微的调整,如果写成CSS类,也只能在这个类上应用。就算有其他的类,也需要相似的效果,但是效果的数值不同,就不能够应用这个类,那我就不得不再写一个给这个元素使用的类。

这种情况真的太常见了,如果给每个元素都写一个类,将造成维护上的困难,调整样式的时候,我不得不在HTML和CSS之间来回的切换页面,如果CSS种有伪类等变体语法,维护起来也更加困难——我需要先找出这个类相关的所有类。因为CSS的同一种页面效果实现不只有一种途径,而且不同的实现途径之间是不能够兼容写法的。比如,我要让div下的元素靠右,有好几种写法,如果div是flex布局或者是grid布局,都能实现靠右的效果,但是其子级的实现却会变得大不相同。所以,当我编写其子级的时候,经常不得不先去看它的父类的实现。

所以当后来我接触到了bootstrap中的功能类时,我想到,为什么不把语义类里的实现展开到HTML的class属性里呢。我便开始了功能类优先的写法。

功能类优先有何不同

这种写法的重点的是优先这两个字,而不是完全的追求功能类写法。在上文中我描述到,在使用语义类经常遇到预先编写的语义类不满足新的元素的情况,而参杂了语义类以外的写法。这种情况下,这个预先编写好的类如果不能够达到重用的效果,那么是否使用语义类写法也就无妨了。所以我将大量语义类的转变为功能类的写法,其效果也是很明显的,它大量减少了CSS文件的数量,HTML的样式实现也更加直观——因为直接写在了class上了,也不需要反复的切换文件去查找该类的实现。最重要的一点是,完全消除了style的写法,代码变得风格统一且相对美观。

例子如下:

figure class=flex rounded p-8 figure

上述的例子中的class含义可能难以理解,它们是功能类的写法,直接理解为CSS的实现语句会容易一点,如:flex就是display flex; p-8就是padding 8px; rounded就是border-radius .75rem; 当然,具体的数据跟含义依赖于你所引用的三方库的实现。

这种写法,一开始我跟很多第一次接触到功能类写法的人一样,很不理解,认为会给我增加代码数,且class类名变得很长会难以维护,但是当我实际使用后,我发现我的顾虑是多余的。首先的是功能类的写法提供了更少的CSS文件——通常指需要一个全局CSS文件;更少的字母数——如text-center代表align-text center;;更加直观的实现——所有的实现都直接写在class的属性值中。

我们上面论述到CSS语义类的重用是很艰难的,对着这些无法重用的CSS类,完全可以写成功能类写法,以规避其短处。像容器元素以及子级的元素,因为HTML的嵌套结构的限制,使得CSS类必须服务于这种嵌套,无法重用,那么就干脆不考虑重用这一块,全部展开成功能类好了。

当然,对于那些真正能够达到重用效果的CSS,使用语义类写法还是非常推荐的,尽管这种情况非常稀少。两者择其优之,杂用语义和功能,功能类的写法会更多,所以叫功能类优先。

重点在于要理解“优先”这两个字。

那么,不好用语义类的写法应该使用在什么地方呢?

语义类就不用了吗

语义类写法应当应用在能够充分发挥其重用、于HTML结构无关的地方。充分发挥重用,在于我将语义类应用到元素上后,不需要再写其他的CSS语句来进行调整。如果不能够重用,那还不如写功能类,以消除CSS文件。HTML元素的结构不同会影响到CSS的表现,使其重用功能受到影响,所以一个稳定的元素是应用语义类的前提。

在以上两个前提下,最合适应用的地方就是全局的设置和button类的设置。

全局的设置其实和重用无关也和元素结构无关,在全局的CSS文件里写上root属性可以方便地调整主题,也可以对HTML元素做出默认的设置,如默认消除body的margin。对于button元素,在于通常它是最小的单独使用元素单位,即与其他结构无关,不管在哪种结构中其样式都是固定的少数几种,最小单位是说它通常不会再嵌套其他的元素了。这两个特点使得button这种元素的性质十分稳定,语义类的写法非常合适于它。关于它的CSS类就可以写在全局的CSS文件中,达到全局的重用。然而这种类型的元素是少数。

常见问答

问:功能类优先的写法不会让人难以理解吗?

答:如果是说功能类在元素上展开了大量的类使得class属性值很长,那么是不用担心的。首先,你能够使用多少功能类,完全取决于你能够使用多少种原生的CSS语句,所以你完全不可能使用你不理解的功能类。如果担心使用功能类使得class属性值很长,请试想一点,难道将这些属性写在CSS类里,要写的字数就会变少了吗?功能类,不过是将原本在CSS类中的内容搬到的class属性中,该写多少还是会写多少。甚至是由于第三方库的优秀支持,使功能类的类名更加短小,在代码的编写上只会比语义类更方便。

问:多个功能类在class属性中的值太长,会不会使HTML难以阅读?

答:我强烈建议你试用功能类,这会让你更容易理解功能类的写法和阅读比起语义里更方便。当我调试语义类的时候,在开发人员工具(F12)中,我们会走以下流程:找到我们要调试的元素——找到它的类名——在开发人员工具栏中找到类名的实现——查看实现语句。在这个过程中,我们其实只关注开始的元素和结尾的实现语句,中间的流程可以说使因为语义类的写法而不得已存在的。如果我们使用功能为,这个流程就会变成:找到我们要调试的元素——查看class属性值。由于功能类的特性,当我们查看功能类的特性时也就是在查看其实现,完全省略了中间不必要的过程,只会更方便。如果你担心class属性值过长,那么只需要交给你的编辑器就行了,就像是应对你的JS代码,有各种lint工具来为你做这些事。

问:是否功能类一定要使用第三方UI库?

答:如果你的项目里对样式的要求不高不复杂,那么完全不需要任何第三方库,就算写成语义类也是可以的。如果项目的要求的样式复杂,使用第三方UI库来写功能类优先写法是更明智的选择。毕竟你一定不想把每个常用功能都实现一遍。

问:有推荐的第三方UI库吗?

答:tailwindcss