写点什么

如何在 React 中优雅的写 CSS

  • 2021-03-08
  • 本文字数:3897 字

    阅读完需:约 13 分钟

如何在 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、伪元素和媒体查询(http://reactcss.com/)


看下官网文档 :

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 方案(https://www.styled-components.com/)


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>)
复制代码


方案三: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 ,但是谁优谁胜无法武断。


  • 如果你的团队还没有使用这任一技术,需要考虑的是团队成员的感受

  • 如果已经在使用其中某一种方案,保持一致性即可,相信并这样走下去



头图:Unsplash

作者:七喜

原文:https://mp.weixin.qq.com/s/c0zbwrqDQOAhwZulNV4Dtw

原文:如何在 React 中优雅的写 CSS

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-03-08 23:525157

评论 2 条评论

发布
用户头像
如果你更喜欢vue的scope方案也可以在react里用——scoped-css-loader
2021-03-12 17:37
回复
用户头像
目前我们微前端项目也遇到了这种问题,我负责的项目是采用的namespace,另一个同学的项目用的 CSS Modules,一次规范对齐会上也谈了这件事是否要保持一致,我们都各有考虑,最后 Leader 的结论也是保持自己项目内风格一致就行
2021-03-09 10:42
回复
没有更多了
发现更多内容

区块链之共识算法系列——PoW(二)

Regan Yue

区块链 11月日更 趣讲区块链

研发提效:通过设计稿自动生成前端代码。

石云升

大前端 自动生成 11月日更 研发提效

质量基础设施一站式云服务平台搭建,NQI一站式服务平台

电微13828808271

区块链符号理论:符号方案和符号系统

CECBC

Prometheus HTTP API 查询(六)Prometheus Server 状态和 TSDB 状态

耳东@Erdong

Prometheus PromQL HTTP API 11月日更

互联网大厂考点(阿里+百度+腾讯+字节跳动+美团+京东)

Java高级开发

Java 程序员 架构 面试 大厂

鸿蒙的绿野仙踪

脑极体

Alibaba Druid 源码阅读(二) 数据库连接池实现初步探索

数据库

送给正在入行的小白:最全最有用的网络安全学习路线已经安排上了

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

自定义View:如何实现手动拖动的图片控件

Changing Lin

11月日更

详解低延时高音质:回声消除与降噪篇

声网

回声消除 音频体验 RTE技术详解

四大研究成果入选 百度安全在Black Hat Europe 2021再创历史

百度安全

应用安全 百度安全 Black Hat 漏洞利用 恶意软件

Alibaba Druid 源码阅读(一) 数据库连接池初步

数据库

【得物技术】得物直播秒开体验提升

得物技术

体验 直播 视频 优化 卡顿

GrowingIO Design 图标库:从原理到实现

GrowingIO技术专栏

SVG 图标库 Sprites Icon Font GrowingIO Design

阿里灵杰产品全面拥抱云原生,双11优惠让企业体会上云便利

阿里云大数据AI技术

大数据 AI 云原生 阿里灵杰

C++ 动态新闻推送 第35期

很水

终于有人把“元宇宙”说清楚了

CECBC

接口调用的链路

卢卡多多

接口 11月日更

12 K8S之应用容器及Pod资源

穿过生命散发芬芳

k8s 11月日更

2021年10月券商App行情刷新及交易体验评测报告,兴业证券荣登榜首!

博睿数据

Python代码阅读(第57篇):倒序字符串

Felix

Python 编程 string 阅读代码 Python初学者

ZGC在合合信息HBase平台中的实践

合合信息大数据团队

大数据 性能优化 ZGC HBase 合合信息

C++ 动态新闻推送 第36期

很水

c++

当一名科技女性,决定不再做月亮

脑极体

Alibaba Druid 源码阅读(三) 数据库连接池初始化探索

数据库

Alibaba Druid 源码阅读(五)数据库连接池 连接关闭探索

数据库

网易云信携手“瑶台”,打造元宇宙商业化实践标杆案例

网易云信

人工智能 虚拟化 虚拟人 元宇宙

投资元宇宙,如何开启混合现实开发

龙智—DevSecOps解决方案

游戏开发 元宇宙 混合现实

Alibaba Druid 源码阅读(四) 数据库连接池中连接获取探索

数据库

模块三-学生管理系统架构设计文档

迪马

如何在 React 中优雅的写 CSS_语言 & 开发_政采云前端团队_InfoQ精选文章