zoukankan      html  css  js  c++  java
  • 如何在 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;
       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;
    }

    vi设计http://www.maiqicn.com 办公资源网站大全https://www.wode007.com

    总结

    (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 ,但是谁优谁胜无法武断。

    • 如果你的团队还没有使用这任一技术,需要考虑的是团队成员的感受
    • 如果已经在使用其中某一种方案,保持一致性即可,相信并这样走下去
  • 相关阅读:
    获取随机数
    性能测试工具
    Oracle 级联删除
    一些shell用法
    英文
    主题:【元宵赏灯】蛇年杭州元宵赏灯攻略(上城区、滨江区、下城区)
    CListCtrl 列表选中项非焦点时也是藍色
    ASCII码表
    杭州市公积金提取及相关知识
    ListBox设置水平滚动条
  • 原文地址:https://www.cnblogs.com/xiaonian8/p/13761194.html
Copyright © 2011-2022 走看看