前言

本文主要描述的组织css的问题,文章内容整理自《前端架构设计》的读书笔记。

继承与特性之争

  1. h2{
  2. }
  3. #sidebar h2{
  4. background:red;
  5. }
  6. #sidebar .calendar h2{
  7. background:green;
  8. }

上面这样写样式有很大的问题,主要是我们在样式书写时默认的继承到了之前的样式,但在特殊化的样式时,为了让自己的样式生效,又要新写样式进行覆盖。
所以列举一下可能的问题是下面的:

  • 选择器的优先级:选择合适的优先级,查询效率高,方便其他人样式覆盖的需求
  • 颜色重置:要恢复到原来的颜色,需要进行不断的重置或者赋值
  • 位置依赖:如果我们的样式移到其他位置,那么样式代码就会失效,因为严格依赖dom的结构和顺序,不能很好的解耦
  • 多重继承:最终元素的样式可能是层层嵌套得到的最终样式代码,非常不可复用,当改变主体的样式时,子元素都会受到影响
  • 深层嵌套:效率非常低

    现代化的模块方案

在之前的章节中,我们讲到了css不同的模块化方案,那么对于上面描述的问题,其实可以很简化的去解决。

案例代码

你根据以上的需求按照模块的方案和命名,去除各种依赖,最大程度使用class命名方式,减少继承,更大程度的使用独立样式代码块,轻松解决了以上的问题。

  1. // 组件文件夹
  2. .content_title{}
  3. // 不用恢复重置样式
  4. .contitle_title--reverse{}
  5. // 特殊组件的title定义
  6. .calendar_title{
  7. }

单一职责原则

比如我们在定义标题的时候,可能有两个模块都用到了标题。

  1. .primary-title{
  2. }

可能的问题,是我们需要在日历或者博客的时候需要去修改这个样式,于是你的样式可能变成这样的。

  1. .primary-title{
  2. }
  3. .blog .primary-title{}

这样的问题是会导致选择器过多,于是我们按照bem进一步优化。

  1. .calendar_header{}
  2. .blog_header{}

这样维护之后,每个样式块都只负责自己的内容,除了导致代码的重复之外没有任何坏处。好处是如果项目严格执行单一职责方式,当我们改动任何代码的时候,不会担心对全局造成的影响。那么重复的代码怎么解决,这个其实webpack的部分和gzip的部分已经能把我们的代码进行优化了。
延伸思考:如果压缩打包的角度可以去掉重复代码的部分,那么更多角度考虑代码的可持续、可维护就好了。

单一样式来源

简单来讲,就是你的样式代码应该来源于尽可能少的组件,最好是维护在一个class中,保证整个样式代码是可追溯的,也可以预想效果的。

案例

  1. /* calendar css*/
  2. .calendar-header{
  3. }
  4. /* blog css*/
  5. .blog-header{
  6. }
  7. .blog .blog-header{}

以blog进行title的限定,主要是限制让字号小一点,这种的主要问题是当项目积累下来,会有很多样式代码散落在各个组件,这样导致不可追溯。(补充个常识,以父元素为什么进行修饰子元素,这个称为样式上下文)
那么建议的方式是将散落在各处的代码完全控制在一个组件内。

组件修饰符

虽然单一组件的原则让我们对组件的样式代码封装的很好,但还是有一些特殊的需求,那我们如何设计这方面的代码呢?通过皮肤或者子模块来实现,也可以称为组件修饰符。
比如我们针对日历的组件,我们需要追加一个特殊的样式,可以这样实现。

  1. .calendar-header{
  2. }
  3. .calendar--nested .calendar-header{
  4. }

通过这样的方式,我们不但可以实现皮肤的需求,也可以实现对上下文的解耦,我们只要关注组件需要什么样的特殊样式,进行组件修饰即可,而不用依赖耦合在父容器里。

element-ui的css

以下以element的2.4.11版本为例,根据其api文档以及源码的角度为大家分析element-ui是如何解决这些问题的。

el-button分析

  • el-button文档地址
  • api设计规范:可以看到button的暴露的api主要是基于属性的
    | 参数 | 说明 | 类型 | 可选值 | 默认值 | | —- | —- | —- | —- | —- | | size | 尺寸 | string | medium / small / mini | — | | type | 类型 | string | primary / success / warning / danger / info / text | — | | plain | 是否朴素按钮 | boolean | — | false | | round | 是否圆角按钮 | boolean | — | false | | circle | 是否圆形按钮 | boolean | — | false | | loading | 是否加载中状态 | boolean | — | false | | disabled | 是否禁用状态 | boolean | — | false | | icon | 图标类名 | string | — | — | | autofocus | 是否默认聚焦 | boolean | — | false | | native-type | 原生 type 属性 | string | button / submit / reset | button |
  • class的设计,虽然组件的设计是基于属性的,但实际不同的属性最终都是表现为class的。那么基于这样的角度去设计样式是element-ui的首创或者是vue组件的创新么?其实早在bootstrap里就是这样设计的。
    | class | 说明 | | —- | —- | | el-button-group | 按钮组的样式,作为外部容器,不对el-button产生任何样式代码,但对其内含有的el-button会产生垂直居中的效果,还有向右的间距,.el-button-group .el-button--primary:first-child{} | | el-button el-button—default | 基本样式,修饰符,生效规则el-button—default也是直接定义其样式规则,不依赖于el-button | | el-button el-button—medium | el-button—medium 修饰符中直接定义好尺寸的全部代码 | | el-button el-button—default is-circle | 基本样式,smacss状态样式 ,其中is-circle包含这个原型的全部样式,其生效的条件是与el-button同时生效,is-disabled同理 | | icon=”el-icon-edit” | 带icon的部分不在button里做特殊样式,其样式属于基本样式,但是dom结构是通过组件进行添加的,那么其图标的字号来源于哪里呢?来源于el-button的基本样式14px,所以这部分进行了解耦,不用单独设置 |
  • 组件内如何根据属性进行返回对应的class.
    1. class="el-button"
    2. // 可以看到其根据传入的各个type属性分别将class追加到class数组之中
    3. :class="[type ? 'el-button--' + type : '',buttonSize ? 'el-button--' + buttonSize : '',{'is-disabled': buttonDisabled,'is-loading': loading,'is-plain': plain,'is-round': round,'is-circle': circle}]"
    4. //根据为loading 显示出固定的loading图标
    5. // 根据传入的icon以及没有loading显示出对应type的icon

    轻度定制的element

经常有场景我们需要引入element-ui之后需要对其ui进行皮肤化的开发样式,虽然element-ui有暴露其对应的组件样式,我们也可以进行对应的详细的源码的fork并开发,但在大多数中小公司其实不用小题大做。我们只需要根据自己的情况,针对一样的样式进行全局样式覆盖即可。那么,我们项目中针对按钮的皮肤化改变会是这样的。

  1. // 定义variables.scss的主题变量
  2. // 主题色相关变量
  3. $color-thin:rgba(60,191,196,0.1);
  4. $color:rgba(60,191,196,1);
  5. // customer-element.scss 主要定义已使用饿了么组件样式的修改
  6. @import './variables';
  7. // 一个按钮样式的全局覆盖
  8. .el-button--primary{
  9. &:hover,&:focus{
  10. background:$color;
  11. border-color:$color;
  12. }
  13. background:$color;
  14. border-color:$color;
  15. }

bootstrap的css

我们同样也是分析bootstrap里的样式使用,基本也是使用class拼盘理论的。

button.less样式文件节选,可以看到其基本样式、嵌套内伪类的样式,button-size的方法类,修饰符的(子模块的)维护方式,这都是值得我们借鉴的科学规范思想。

  1. // Base styles
  2. // --------------------------------------------------
  3. .btn {
  4. display: inline-block;
  5. ......
  6. .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);
  7. .user-select(none);
  8. &,
  9. &:active,
  10. &.active {
  11. &:focus,
  12. &.focus {
  13. .tab-focus();
  14. }
  15. }
  16. &:hover,
  17. &:focus,
  18. &.focus {
  19. color: @btn-default-color;
  20. text-decoration: none;
  21. }
  22. a& {
  23. &.disabled,
  24. fieldset[disabled] & {
  25. pointer-events: none; // Future-proof disabling of clicks on `` elements
  26. }
  27. }
  28. }
  29. // Alternate buttons
  30. // --------------------------------------------------
  31. .btn-default {
  32. .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
  33. }
  34. .btn-primary {
  35. .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
  36. }

小结

到此为止总结的技巧点如下:当然肯定还有更多的技巧等待你补充

  • 分离容器与内容
    单一样式来源
    区分布局与组件的角色
    在标记上使用单一的、扁平的标识
    组件修饰符