如何在 React 中优雅的写 CSS?

更新日期: 2019-12-16阅读: 2.1k标签: 代码
本文首发于政采云前端团队博客:如何在 React 中优雅的写 CSS


引言

问题:css 文件分离 != CSS 作用域隔离

看下这样的目录结构:

├── src                                  
│   ├──......                            # 公共组件目录
│   ├── components                       # 组件
│   │   └──comA                          # 组件A
│   │       ├──comA.js                     
│   │       ├──comA.css                      
│   │       └── index.js                  
│   │   └──comB                          # 组件B
│   │       ├──comB.js                     
│   │       ├──comB.css                      
│   │       └── index.js                  
│   ├── routes                           # 页面模块                  
│   │   └── modulesA                     # 模块A
│   │       ├──pageA.js                  # pageA JS 代码
│   │       ├──pageA.css                 # pageA CSS 代码

看目录结构清晰明了,由于“ CSS 文件分离 != CSS 作用域隔离”这样的机制,如果我们不通过一些工具或规范来解决 CSS 的作用域污染问题,会产生非预期的页面样式渲染结果。

假设我们在组件 A 和组件 B import 引入 comA.css 和 comB.css。

comA.css

.title {
    color: red;
}

comB.css

.title {
    font-size: 14px;
}

最后打包出来的结果为:

.title {
    color: red;
}
.title {
    font-size: 14px;
}

我们希望,comA.css 两者互不影响,可以发现,虽然 A、B 两个组件分别只引用了自己的 CSS 文件,但是 CSS 并没有隔离,两个 CSS 文件是相互影响的!

随着 SPA 的流行,js 可以组件化,按需加载(路由按需加载、组件的 CSS 和 JS 都按需加载),这种情况下 CSS 作用域污染的问题被放大,CSS 被按需加载后由于 CSS 全局污染的问题,在加载出其他一部分代码后,可能导致现有的页面上会出现诡异的样式变动。这样的问题加大了发布的风险以及 debugger 的成本。

小编我从写 vue 到写 react,Vue 的 scoped 完美的解决了 CSS 的作用域问题,那么 React 如何解决 CSS 的作用域问题呢?

解决 React 的 CSS 作用域污染方案:

  • 方案一:namespaces
  • 方案二:CSS in JS
  • 方案三:CSS Modules


方案一:namespaces

利用约定好的命名来隔离 CSS 的作用域

comA.css

.comA .title {
    color: red;
}
.comA .……{
    ……
}

comB.css

.comB .title {
    font-size: 14px;
}
.comB .……{
    ……
}

嗯,用 CSS 写命名空间写起来貌似有点累。

没事我们有 CSS 预处理器,利用 less、sass、stylus 等预处理器,代码依然简洁。

A.less

.comA {
    .title {
        color: red;
    }
    
    .…… {
        ……
    }
}

B.less

.comB {
    .title {
        font-size: 14px;
    }
    
    .…… {
        ……
    }
}

貌似很完美解决了 CSS 的作用域问题,但是问题来了,假设 AB 组件是嵌套组件。

那么最后的渲染 dom 结构为:

<div class="comA">
    <h1 class="title">组件A的title</h1>
    <div class="comB">
        <h1 class="title">组件组件的title</h1>
    </div>
</div>

comA 的样式又成功作用在了组件 B 上。

没关系,还有解,所有的 class 名以命名空间为前缀。

<div class="comA">
    <h1 class="comA__title">组件A的title</h1>
    <div class="comB">
        <h1 class="comB__title">组件组件的title</h1>
    </div>
</div>

A.less

.comA {
    &__title {
        color: red;
    }
}

B.less

.comB {
    &__title {
        font-size: 14px;
    }
}

如果,我们的样式还遵循 BEM (Block, Element, Modifier) 规范,那么,样式名简直不要太长!但是问题确实也解决了,但约定毕竟是约定,靠约定和自觉来解决问题毕竟不是好方法,在多人维护的业务代码中这种约定来解决 CSS 污染问题也变得很难。


方案二:CSS in JS

使用 JS 语言写 CSS,也是 React 官方有推荐的一种方式。

从React文档进入

https://github.com/MicheleBertoli/css-in-js ,可以发现目前的 CSS in JS 的第三方库有60余种。

看两个比较大众的库:

  • reactCSS
  • styled-components

reactCSS

支持 React、Redux、React Native、autoprefixed、Hover、伪元素和媒体查询

看下官网文档 :

const styles = reactCSS({
  'default': {
    card: {
      background: '#fff',
      boxShadow: '0 2px 4px rgba(0,0,0,.15)',
    },
  },
  'zIndex-2': {
    card: {
      boxShadow: '0 4px 8px rgba(0,0,0,.15)',
    },
  },
}, {
  'zIndex-2': props.zIndex === 2,
})
class Component extends React.Component {
  render() {
    const styles = reactCSS({
      'default': {
        card: {
          background: '#fff',
          boxShadow: '0 2px 4px rgba(0,0,0,.15)',
        },
        title: {
          fontSize: '2.8rem',
          color: this.props.color,
        },
      },
    })
    return (
      <div style={ styles.card }>
        <div style={ styles.title }>
          { this.props.title }
        </div>
        { this.props.children }
      </div>
    )
  }
}

可以看出,CSS 都转化成了 JS 的写法,虽然没有学习成本,但是这种转变还是有一丝不适。

styled-components

styled-components,目前社区里最受欢迎的一款 CSS in JS 方案
const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;
  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props => props.primary && css`
    background: white;
    color: palevioletred;
  `}
`
render(
  <div>
    <Button
      href="https://github.com/styled-components/styled-components"
      target="_blank"
      rel="noopener"
      primary
    >
      GitHub
    </Button>
    <Button as={Link} href="/docs" prefetch>
      Documentation
    </Button>
  </div>
)

与 reactCSS 不同,styled-components 使用了模板字符串,写法更接近 CSS 的写法。


方案三:CSS Modules

利用 webpack 等构建工具使 class 作用域为局部。

CSS 依然是还是 CSS

例如 webpack ,配置 css-loader 的 options modules: true。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: true,
        },
      },
    ],
  },
};

modules 更具体的配置项参考:https://www.npmjs.com/package/css-loader

loader 会用唯一的标识符 (identifier) 来替换局部选择器。所选择的唯一标识符以模块形式暴露出去。

示例:

webpack css-loader options

options: {
  ...,
  modules: {
    mode: 'local',
    // 样式名规则配置
    localIdentName: '[name]__[local]--[hash:base64:5]',
  },
},
...

App.js

...
import styles from "./App.css";
...
<div>
  <header className={styles["header__wrapper"]}>
    <h1 className={styles["title"]}>标题</h1>
    <div className={styles["sub-title"]}>描述</div>
  </header>
</div>

App.css

.header__wrapper {
  text-align: center;
}

.title {
  color: gray;
  font-size: 34px;
  font-weight: bold;
}

.sub-title {
  color: green;
  font-size: 16px;
}

编译后端的 CSS,classname 增加了 hash 值。

.App__header__wrapper--TW7BP {
  text-align: center;
}

.App__title--2qYnk {
  color: gray;
  font-size: 34px;
  font-weight: bold;
}

.App__sub-title--3k88A {
  color: green;
  font-size: 16px;
}


总结

(1)如果是 ui 组件库中使用

建议使用 namespaces 方案

原因:

  • ui 组件库维护人员基本固定,遵守约定的规范较为容易,可通过约定规范来解决不同组件 CSS 相互影响问题
  • 由于 ui 组件库会应用于整个公司的产品,在真正的业务场景中,虽然不建议,但是可能无法避免需要覆盖组件样式的特殊场景,如使用其他两种方式,不能支持组件样式覆盖

(2)如果是业务代码/业务组件中使用

CSS in JS / CSS Modules

业务代码维护人员较多且不固定、代码水平不一致,只通过规范来约束不靠谱,无法保证开发人员严格遵守规范,不能根治 CSS 交叉影响问题,但是从 debug 角度考虑,建议组件外层都添加一个 namespaces 方面定位组件。然后加之 CSS in JS 或 CSS Modules 方案来解决 CSS 交叉影响问题。

CSS in JS 和 CSS Modules 谁优谁胜?

CSS Modules 会比 CSS in JS 的侵入性更小,CSS in JS 可以和 JS 共享变量,但个人更喜欢 CSS Modules ,但是谁优谁胜无法武断。

  • 如果你的团队还没有使用这任一技术,需要考虑的是团队成员的感受
  • 如果已经在使用其中某一种方案,保持一致性即可,相信并这样走下去


链接: https://fly63.com/article/detial/6862

不要浪费时间写完美代码

一个系统可以维持5年,10年,甚至20年以上,但是代码和设计模式的生命周期非常短,当对一个解决方案使用不同的方法进行迭代的时候,通常只能维持数月,数日,甚至几分钟的时间

Google内部在代码质量上的实践

良好的编程习惯涉及到很多方面,但在软件行业内,大多数的公司或组织都不会把良好的编程习惯列为主要关注点。 例如,具有可读性和可维护性的代码比编写好的测试代码或使用正确的工具更有意义,前者的意义在于可以让代码更易于理解和修改。

减少嵌套,降低代码复杂度

减少嵌套会让代码可读性更好,同时也能更容易的找出bug,开发人员可以更快的迭代,程序也会越来越稳定。简化代码,让编程更轻松!

关于 Google 发布的 JS 代码规范

Google为了那些还不熟悉代码规范的人发布了一个JS代码规范。其中列出了编写简洁易懂的代码所应该做的最佳实践。代码规范并不是一种编写正确JavaScript代码的规则,而是为了保持源代码编写模式一致的一种选择。

你解决的问题比你编写的代码更重要!

程序员似乎忘记了软件的真正目的,那就是解决现实问题。您编写的代码的目的是为了创造价值并使现有世界变得更美好,而不是满足您对自我世界应该是什么的以自我为中心的观点。有人说:如果你拥有的只是一把锤子,那么一切看起来都像钉子一样

tinymce与prism代码高亮实现及汉化的配置

TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能方强大,并且功能配置灵活简单。另一特点是加载速度非常快的。

js函数式编程与代码执行效率

函数式编程对应的是命令式编程, 函数式编程的核心当然是对函数的运用. 而高阶函数(Higher-order)是实现函数式编程的基本要素。高阶函数可以将其他函数作为参数或者返回结果。所以JS天生就支持函数式编程

接手代码太烂,要不要辞职?

朋友发表了一条说说:入职新公司,从重构代码到放弃”,我就问他怎么了?他说,刚进一家新公司,接手代码太烂,领导让我先熟悉业务逻辑,然后去修复之前项目中遗留的bug,实在不行就重构

js高亮显示关键词_页面、搜索关键词高亮显示

页面实现关键词高亮显示:在项目期间遇到一个需求,就是搜索关键词时需要高亮显示,主要通过正则匹配来实现页面关键词高亮显示。在搜索结果中高亮显示关键词:有一组关键词数组,在数组中筛选出符合关键字的内容并将关键字高亮

写优雅的代码,做优雅的程序员

软件工程学什么? 学计算机,写程序,做软件,当程序员。听说学计算机很辛苦? 是的,IT行业加班现象严重。在计算机世界里,技术日新月异,自学能力是程序员最重要的能力之一。选了这个专业,就要时刻保持好奇心和技术嗅觉,不能只满足于完成课内作业。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!