写点什么

如何在 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:525279

评论 2 条评论

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

WPF学习——依赖项属性(2)(1)

Java 程序员 后端

营口市广东商会成立

江湖老铁

架构实战营-模块三作业

随风King

「架构实战营」

【Java 基础语法】万字解析 Java 的多态、抽象类和接口

Java 程序员 后端

【Java 多线程 1】CountDownLatch

Java 程序员 后端

【Java每日面试题】大厂是如何设计秒杀系统的?

Java 程序员 后端

YGC问题排查,又让我涨姿势了!

Java 程序员 后端

Zookeeper(从7个方面来了解Zookeeper基础概念)

Java 程序员 后端

Worktile、Teambition与Tower项目管理软件对比

Java 程序员 后端

WPF学习——依赖项属性(1)

Java 程序员 后端

[译] 微服务的设计模式

Java 程序员 后端

《Spring实战》读书笔记-第4章 面向切面的Spring(1)

Java 程序员 后端

《代码重构》之方法到底多长算“长”

Java 程序员 后端

【C 语言小游戏】手打贪吃蛇1

Java 程序员 后端

windows7 本地搭建ELK 收集项目运行日志

Java 程序员 后端

【Java从0到架构师】SQL 多表查询

Java 程序员 后端

ZooKeeper分布式配置——看这篇就够了

Java 程序员 后端

《吃透MQ系列》核心基础全在这里了,一文啃透!

Java 程序员 后端

「并发原理专题」AQS的技术体系之CLH、MCS锁的原理及实现

Java 程序员 后端

【Java从0到架构师】Maven

Java 程序员 后端

全面通透深入剖析工厂方法模式

Tom弹架构

Java 架构 设计模式

“抽象类”到底抽不抽象?实例对比一看便知!

Java 程序员 后端

《JVM系列》 第六章 -- 对象的实例化与内存布局(1)

Java 程序员 后端

《JVM系列》 第六章 -- 对象的实例化与内存布局

Java 程序员 后端

《重学Java高并发》Disruptor使用实战

Java 程序员 后端

【Java核心面试宝典】Day3、图解HashMap高频面试及底层实现架构!

Java 程序员 后端

WPF学习——依赖项属性(2)

Java 程序员 后端

ZK(ZooKeeper)分布式锁实现

Java 程序员 后端

《Spring实战》读书笔记-第4章 面向切面的Spring

Java 程序员 后端

《菜菜的机器学习sklearn课堂》降维算法PCA和SVD

Java 程序员 后端

【Java 集合框架】Stack、Queue 和 Deque 的使用

Java 程序员 后端

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