前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【React】:CSS 模块化

【React】:CSS 模块化

作者头像
WEBJ2EE
发布2020-07-14 16:56:53
1.2K0
发布2020-07-14 16:56:53
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
1. 为什么 CSS 要模块化?
  1.1. 难以理解
  1.2. 难以维护
2. 什么是CSS模块化?
3. CSS模块化方案——BEM
4. CSS模块化方案——CSS In JS
  4.1. CSS-in-JS 库
  4.2. styled-components 示例
5. CSS模块化方案——CSS Modules

1. 为什么 CSS 要模块化?

模块化 CSS 使用的主要场景是棘手的大规模 CSS。

写代码并不难,难的是在不让你的代码随着时间的推移成为拖累你的“技术债”。

1.1. 难以理解

以下是 CSS Guidelines 中的一个示例,这个示例展示了一个问题:除了写这段代码的人,没有人知道这段代码是干什么的。

代码语言:javascript
复制
<div class="box profile pro-user">
  <img class="avatar image" />
  <p class="bio">...</p>
</div>
  • box 和 profile 有什么关系?
  • profile 和 avatar 有什么关系?或者他们之间真的有关系吗?
  • 你应该在 bio 旁边添加 pro-user 吗?
  • image 和 profile 写在同一部分 CSS 吗?
  • 可以在其他地方使用 avatar 吗?

光看代码无法回答这些问题,你必须在 CSS 代码中推理他们的作用。

1.2. 难以维护

大规模的 CSS 也难以维护。

  • 当你改变了一个标签,样式就会像纸牌屋一样崩溃。
  • 当你想更新一个页面上的样式,却破坏了另一个页面的样式。
  • 当你试图覆盖其他页面,但又深陷于优先度问题。

barstool:n. 酒吧高脚凳 百度翻译

2. 什么是 CSS 模块化?

模块化 CSS 需要你换一个角度看问题,不从页面级别考虑,而是关注组成页面的小块。

这不是一个页面而是一个组件的集合。你会发现页面里包含的是 logo,搜索栏,导航,照片列表,辅助导航,标签框,视频播放器等。这些是可以网站的任何位置都可以独立使用的内容。它们只是碰巧在这个特定页面以这种方式组合。

3. CSS模块化方案——BEM

BEM,三个字母分别代表 Block、Element、Modifier,BEM 也是在 2009 年提出,起源于 Yandex(可以说是俄语版的 Google)。

BEM的核心概念是 —— 块(Block)由子元素(Element)构成,并且可以修改(Modified)。

BEM 命名约定:

代码语言:javascript
复制
.block-name__element--modifier
  • 名称以小写字母书写
  • 名称中的单词用连字符(-)分隔
  • 元素由双下划线(__)分隔
  • 修饰符由双连字符(--)分隔

一个 BEM 例子:

代码语言:javascript
复制
<button class="btn btn--big btn--orange">
  <span class="btn__price">$9.99</span>
  <span class="btn__text">Subscribe</span>
</button>

即使不看 CSS 代码,就能看出这段代码会创建一个“大的”、“橙色”的价格按钮。

另一个 BEM 例子:

代码语言:javascript
复制
<section class="widget">
    <h1 class="widget__header">Sterling Calculator</h1>
    <form class="widget__form" action="process.php" method="post">
        <p>Please enter an amount: (e.g. 92p, &pound;2.12)</p>
        <p>
            <input name="amount" 
                   class="widget__input widget__input--amount"> 
            <input type="submit" value="Calculate" 
                   class="widget__input widget__input--submit">
        </p>
    </form>
</section>

<style>
.widget {
    background-color: #FC3;
}

.widget__header {
    color: #930;
    font-size: 3em;
    margin-bottom: 0.3em;
    text-shadow: #FFF 1px 1px 2px;
}

.widget__input {
    border-radius: 5px;
    font-size: 0.9em;
    line-height: 1.3;
    padding: 0.4em 0.7em;
}

.widget__input--amount {
    border: 1px solid #930;
}

.widget__input--submit {
    background-color: #EEE;
    border: 0;
}
</style>

4. CSS模块化方案——CSS In JS

CSS-in-JS 是一种编程思想,即:用 JS 语言来写 CSS,而不是独立为一些 .css,.scss 或者 less 之类的文件,借助 JS 的语言特性来为 CSS 提供灵活、可扩展的样式定义。

4.1. CSS-in-JS 库

CSS-in-JS 有很多实现方案,常见的有:

  • styled-components:https://styled-components.com/
  • emotion:https://emotion.sh/docs/introduction

4.2. styled-components 示例

代码语言:javascript
复制
import React from "react"
import ReactDOM from "react-dom"

import styled from 'styled-components'

function App() {
    // Create a Title component that'll render an <h1> tag with some styles
    const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

    // Create a Wrapper component that'll render a <section> tag with some styles
    const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

    // Use Title and Wrapper like any other React component – except they're styled!
    return <Wrapper>
        <Title>
            Hello World!
        </Title>
    </Wrapper>;
}

ReactDOM.render(<App/>, document.getElementById("app"));

5. CSS模块化方案——CSS Modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. https://github.com/css-modules/css-modules

Umijs 对 CSS Modules 特性的集成,非常人性化,值得学习:

Umi 会自动识别 CSS Modules 的使用,你把他当做 CSS Modules 用时才是 CSS Modules。 比如: // CSS Modules import styles from './foo.css'; // 非 CSS Modules import './foo.css'; https://umijs.org/docs/assets-css#css-modules

Umijs 的这个特性的实现原理为:

  1. webpack 的 css-loader 本身就支持 CSS Modules(由 modules 属性开启) 。
  2. webpack 的 rule 支持 oneOf + resourceQuery 的分支条件。
    1. 对 import "xx.css" 写法,采用全局模式,不采用模块化模式。
    2. 对 import xx from "xx.css?modules" 采用局部模块化模式。
  3. 利用 babel 将 import xx from "xx.css?modules" 的语法简化。
    1. 对 import "xx.css" 写法,不做任何转换。
    2. 对 import xx from "xx.css" 写法,自动转换为 import xx from "xx.css?modules"

原理分析:

webpack.config.js:【oneOf + resourceQuery】

代码语言:javascript
复制
{
    test: /\.css$/,
    oneOf:[
        {
            resourceQuery: /modules/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: mode === 'development' ? true : false
                    }
                },
                {
                    loader: 'css-loader',
                    options: {
                        modules: {
                            localIdentName: '[path][name]__[local]--[hash:base64:5]',
                        },
                    },
                },
                'postcss-loader'
            ]
        },
        {
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: mode === 'development' ? true : false
                    }
                },
                {
                    loader: 'css-loader',
                },
                'postcss-loader'
            ]
        }
    ]
},
{
    test: /\.less$/,
    oneOf:[
        {
            resourceQuery: /modules/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: mode === 'development' ? true : false
                    }
                },
                {
                    loader: 'css-loader',
                    options: {
                        modules: {
                            localIdentName: '[path][name]__[local]--[hash:base64:5]',
                        },
                    },
                },
                'postcss-loader',
                {
                    loader: 'less-loader',
                    options: {
                        javascriptEnabled: true
                    }
                }
            ]
        },
        {
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: mode === 'development' ? true : false
                    }
                },
                {
                    loader: 'css-loader',
                },
                'postcss-loader',
                {
                    loader: 'less-loader',
                    options: {
                        javascriptEnabled: true
                    }
                }
            ]
        }
    ]
},

babel 插件:【自动添加 ?modules 后缀】

代码语言:javascript
复制
const {extname} = require('path');

const CSS_EXTNAMES = ['.css', '.less', '.sass', '.scss', '.stylus', '.styl'];

module.exports = function () {
    return {
        visitor: {
            ImportDeclaration(path) {
                const {specifiers, source} = path.node;
                const {value} = source;
                if (specifiers.length && CSS_EXTNAMES.includes(extname(value))) {
                    source.value = `${value}?modules`;
                }
            },
        }
    };
}

global.css:【全局样式-css

代码语言:javascript
复制
.global {
    font-size: 40px;
}

another-global.less:【全局样式-less

代码语言:javascript
复制
.another-global {
    font-size: 40px;
}

scoped.css:【局部样式-css

代码语言:javascript
复制
.container {
    padding: 4em;
    background: papayawhip;
}

.title {
    font-size: 1.5em;
    text-align: center;
    color: palevioletred;
}

another-scoped.less:【局部样式-less

代码语言:javascript
复制
.container {
    padding: 2em;
    background: goldenrod;
}

.title {
    font-size: 2em;
    text-align: left;
    color: palegoldenrod;
}

index.tsx:【程序入口】

代码语言:javascript
复制
import React from "react"
import ReactDOM from "react-dom"

import "./global.css"
import scoped from "./scoped.css"

import "./another-global.less"
import another_scoped from "./another-scoped.less"

function App() {
    // Use Title and Wrapper like any other React component – except they're styled!
    return <div>
        <section className={scoped.container}>
            <h1 className={scoped.title}>
                Hello World!
            </h1>
        </section>
        <section className={another_scoped.container}>
            <h1 className={another_scoped.title}>
                Hello World!
            </h1>
        </section>
    </div>;
}

ReactDOM.render(<App/>, document.getElementById("app"));

运行结果:

观察 CSS Modules 是怎么处理局部样式的:

参考:

High-level advice and guidelines for writing sane, manageable, scalable CSS: https://cssguidelin.es/#naming-conventions-in-html Regarding CSS’s Global Scope: https://css-tricks.com/regarding-css-global-scope/?utm_medium=email&utm_source=mobilewebweekly What is Modular CSS? https://spaceninja.com/2018/09/18/what-is-modular-css/ -------------------------------------BEM: BEM: http://getbem.com/introduction/ Maintainable CSS with BEM: https://www.integralist.co.uk/posts/bem/#4 -------------------------------------CSS in JS: styled-components: https://styled-components.com/ -------------------------------------CSS Modules: CSS Modules: https://github.com/css-modules/css-modules css-loader: https://github.com/webpack-contrib/css-loader#modules umijs: https://umijs.org/docs/assets-css#css-modules --------------------------------------Webpack role.oneof: https://webpack.js.org/configuration/module/#ruleoneof role.resourceQuery: https://webpack.js.org/configuration/module/#ruleresourcequery css-loader: https://webpack.js.org/loaders/css-loader/#modules


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档