金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > 未来的编码方式

未来的编码方式

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-05 18:52

CSS Modules 详解及 React 中实践

2016/01/18 · CSS · CSS Modules, React

原文出处: pure render - camsong   

金沙棋牌官方平台 1

CSS 是前端领域中进化最慢的一块。由于 ES2015/2016 的快速普及和 Babel/Webpack 等工具的迅猛发展,CSS 被远远甩在了后面,逐渐成为大型项目工程化的痛点。也变成了前端走向彻底模块化前必须解决的难题。

CSS 模块化的解决方案有很多,但主要有两类。一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式。Radium,jsxstyle,react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS,:hover:active 伪类处理起来复杂。另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是 CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力,API 简洁到几乎零学习成本。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。是我认为目前最好的 CSS 模块化解决方案。近期在项目中大量使用,下面具体分享下实践中的细节和想法。

 

CSS Modules 入门及 React 中实践

2017/03/25 · CSS · React

原文出处: AlloyTeam   

前言

这是Glen Maddern发布于2015年8月19日的一篇文章,主要是之前翻译的文章《理解CSS模块方法》里提到这篇文章,现在算是顺藤摸瓜跟进来看看。

这里的翻译都是根据我自己的理解进行的,所以不是一句一句的来的,有哪些不对的也在所难免,水平有限,希望大家指出。

CSS 模块化遇到了哪些问题?

CSS 模块化重要的是要解决好两个问题:CSS 样式的导入和导出。灵活按需导入以便复用代码;导出时要能够隐藏内部作用域,以免造成全局污染。Sass/Less/PostCSS 等前仆后继试图解决 CSS 编程能力弱的问题,结果它们做的也确实优秀,但这并没有解决模块化最重要的问题。Facebook 工程师 Vjeux 首先抛出了 React 开发中遇到的一系列 CSS 相关问题。加上我个人的看法,总结如下:

  1. 全局污染

CSS 使用全局选择器机制来设置样式,优点是方便重写样式。缺点是所有的样式都是全局生效,样式可能被错误覆盖,因此产生了非常丑陋的 !important,甚至 inline !important 和复杂的[选择器权重计数表](Selectors Level 3),提高犯错概率和使用成本。Web Components 标准中的 Shadow DOM 能彻底解决这个问题,但它的做法有点极端,样式彻底局部化,造成外部无法重写样式,损失了灵活性。

  1. 命名混乱

 

由于全局污染的问题,多人协同开发时为了避免样式冲突,选择器越来越复杂,容易形成不同的命名风格,很难统一。样式变多后,命名将更加混乱。

  1. 依赖管理不彻底

组件应该相互独立,引入一个组件时,应该只引入它所需要的 CSS 样式。但现在的做法是除了要引入 JS,还要再引入它的 CSS,而且 Saas/Less 很难实现对每个组件都编译出单独的 CSS,引入所有模块的 CSS 又造成浪费。JS 的模块化已经非常成熟,如果能让 JS 来管理 CSS 依赖是很好的解决办法。Webpack 的 css-loader 提供了这种能力。

  1. 无法共享变量

复杂组件要使用 JS 和 CSS 来共同处理样式,就会造成有些变量在 JS 和 CSS 中冗余,Sass/PostCSS/CSS 等都不提供跨 JS 和 CSS 共享变量这种能力。

  1. 代码压缩不彻底

由于移动端网络的不确定性,现在对 CSS 压缩已经到了变态的程度。很多压缩工具为了节省一个字节会把 ’16px’ 转成 ‘1pc’。但对非常长的 class 名却无能为力,力没有用到刀刃上。

上面的问题如果只凭 CSS 自身是无法解决的,如果是通过 JS 来管理 CSS 就很好解决,因此 Vjuex 给出的解决方案是完全的 CSS in JS,但这相当于完全抛弃 CSS,在 JS 中以 Object 语法来写 CSS,估计刚看到的小伙伴都受惊了。直到出现了 CSS Modules。

 

写在前面

读文先看此图,能先有个大体概念:

金沙棋牌官方平台 2

阅读本文需要 11m 24s。

正文

如果想在最近CSS开发思想上找到一个转变点,最好去找Christopher Chedeau 2014年11月在NationJS上发表的“css in js”演讲。这是一个分界线,各种不同的思想,就像高速粒子似的在自己的方向上快速发展。例如:在React及一些依赖React的项目中写样式, React Style,jsxstyle和Radium是其中三个,最新的,最巧妙的,和最可行的方法。如果发明是在一种探索的情况下相邻的可能(adjacent possible),那么christopher是创造了很多接近相邻(adjacent)可能性。

金沙棋牌官方平台 3

这些问题,以不同的形式存在于大的CSS代码库中。christopher指出,这些问题都可能通过在js中写代码来解决。但这种解决方案引入了其自身的复杂性和特性。只要看一下,在之前提到的项目中(React Style,jsxstyle和Radium),处理在:hover状态下range的方法。这个问题,在浏览器端css中已经早被解决了。

CSS Modules team找到问题的关键--保持和CSS一致,使用styles-in-js的方式编写。虽然我们还是坚持看好使用了CSS的形式,但还有要感谢对我们提供很多建议的朋友。

我们一直在绞尽脑汁地思考CSS,怎样去编写更好。

CSS Modules 模块化方案

金沙棋牌官方平台 4

CSS Modules 内部通过 [ICSS](css-modules/icss · GitHub) 来解决样式导入和导出这两个问题。分别对应 :import:export 两个新增的伪类。

JavaScript

:import("path/to/dep.css") { localAlias: keyFromDep; /* ... */ } :export { exportedKey: exportedValue; /* ... */ }

1
2
3
4
5
6
7
8
:import("path/to/dep.css") {
  localAlias: keyFromDep;
  /* ... */
}
:export {
  exportedKey: exportedValue;
  /* ... */
}

 

但直接使用这两个关键字编程太麻烦,实际项目中很少会直接使用它们,我们需要的是用 JS 来管理 CSS 的能力。结合 Webpack 的 css-loader 后,就可以在 CSS 中定义样式,在 JS 中导入。
启用 CSS Modules

JavaScript

// webpack.config.js css?modules&localIdentName=[name]__[local]-[hash:base64:5]

1
2
// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]

金沙棋牌官方平台,加上 modules 即为启用,localIdentName 是设置生成样式的命名规则。

JavaScript

/* components/Button.css */ .normal { /* normal 相关的所有样式 */ } .disabled { /* disabled 相关的所有样式 */ }

1
2
3
/* components/Button.css */
.normal { /* normal 相关的所有样式 */ }
.disabled { /* disabled 相关的所有样式 */ }

JavaScript

// components/Button.js import styles from './Button.css'; console.log(styles); buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

1
2
3
4
// components/Button.js
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

生成的 HTML 是

<button class="button--normal-abc53">Submit</button>

1
<button class="button--normal-abc53">Submit</button>

 

注意到 button--normal-abc53 是 CSS Modules 按照 localIdentName 自动生成的 class 名。其中的 abc53 是按照给定算法生成的序列码。经过这样混淆处理后,class 名基本就是唯一的,大大降低了项目中样式覆盖的几率。同时在生产环境下修改规则,生成更短的 class 名,可以提高 CSS 的压缩率。

上例中 console 打印的结果是:

JavaScript

Object { normal: 'button--normal-abc53', disabled: 'button--disabled-def886', }

1
2
3
4
Object {
  normal: 'button--normal-abc53',
  disabled: 'button--disabled-def886',
}

CSS Modules 对 CSS 中的 class 名都做了处理,使用对象来保存原 class 和混淆后 class 的对应关系。

通过这些简单的处理,CSS Modules 实现了以下几点:

  • 所有样式都是 local 的,解决了命名冲突和全局污染问题
  • class 名生成规则配置灵活,可以此来压缩 class 名
  • 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
  • 依然是 CSS,几乎 0 学习成本

样式默认局部

使用了 CSS Modules 后,就相当于给每个 class 名外加加了一个 :local,以此来实现样式的局部化,如果你想切换到全局模式,使用对应的 :global

:local:global 的区别是 CSS Modules 只会对 :local 块的 class 样式做 localIdentName 规则处理,:global 的样式编译后不变。

JavaScript

.normal { color: green; } /* 以上与下面等价 */ :local(.normal) { color: green; } /* 定义全局样式 */ :global(.btn) { color: red; } /* 定义多个全局样式 */ :global { .link { color: green; } .box { color: yellow; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.normal {
  color: green;
}
 
/* 以上与下面等价 */
:local(.normal) {
  color: green;
}
 
/* 定义全局样式 */
:global(.btn) {
  color: red;
}
 
/* 定义多个全局样式 */
:global {
  .link {
    color: green;
  }
  .box {
    color: yellow;
  }
}

Compose 来组合样式

对于样式复用,CSS Modules 只提供了唯一的方式来处理:composes 组合

JavaScript

/* components/Button.css */ .base { /* 所有通用的样式 */ } .normal { composes: base; /* normal 其它样式 */ } .disabled { composes: base; /* disabled 其它样式 */ }

1
2
3
4
5
6
7
8
9
10
11
12
/* components/Button.css */
.base { /* 所有通用的样式 */ }
 
.normal {
  composes: base;
  /* normal 其它样式 */
}
 
.disabled {
  composes: base;
  /* disabled 其它样式 */
}

JavaScript

import styles from './Button.css'; buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

1
2
3
import styles from './Button.css';
 
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

生成的 HTML 变为

<button class="button--base-fec26 button--normal-abc53">Submit</button>

1
<button class="button--base-fec26 button--normal-abc53">Submit</button>

由于在 .normal 中 composes 了 .base,编译后会 normal 会变成两个 class。

composes 还可以组合外部文件中的样式。

JavaScript

/* settings.css */ .primary-color { color: #f40; } /* components/Button.css */ .base { /* 所有通用的样式 */ } .primary { composes: base; composes: primary-color from './settings.css'; /* primary 其它样式 */ }

1
2
3
4
5
6
7
8
9
10
11
12
13
/* settings.css */
.primary-color {
  color: #f40;
}
 
/* components/Button.css */
.base { /* 所有通用的样式 */ }
 
.primary {
  composes: base;
  composes: primary-color from './settings.css';
  /* primary 其它样式 */
}

 

对于大多数项目,有了 composes 后已经不再需要 Sass/Less/PostCSS。但如果你想用的话,由于 composes 不是标准的 CSS 语法,编译时会报错。就只能使用预处理器自己的语法来做样式复用了。
class 命名技巧

CSS Modules 的命名规范是从 BEM 扩展而来。BEM 把样式名分为 3 个级别,分别是:

  • Block:对应模块名,如 Dialog
  • Element:对应模块中的节点名 Confirm Button
  • Modifier:对应节点相关的状态,如 disabled、highlight

综上,BEM 最终得到的 class 名为 dialog__confirm-button--highlight。使用双符号 __-- 是为了和区块内单词间的分隔符区分开来。虽然看起来有点奇怪,但 BEM 被非常多的大型项目和团队采用。我们实践下来也很认可这种命名方法。

CSS Modules 中 CSS 文件名恰好对应 Block 名,只需要再考虑 Element 和 Modifier。BEM 对应到 CSS Modules 的做法是:

JavaScript

/* .dialog.css */ .ConfirmButton--disabled { /* ... */ }

1
2
3
4
/* .dialog.css */
.ConfirmButton--disabled {
  /* ... */
}

你也可以不遵循完整的命名规范,使用 camelCase 的写法把 Block 和 Modifier 放到一起:

JavaScript

/* .dialog.css */ .disabledConfirmButton { }

1
2
3
/* .dialog.css */
.disabledConfirmButton {
}

如何实现CSS,JS变量共享

注:CSS Modules 中没有变量的概念,这里的 CSS 变量指的是 Sass 中的变量。

上面提到的 :export 关键字可以把 CSS 中的 变量输出到 JS 中。下面演示如何在 JS 中读取 Sass 变量:

JavaScript

/* config.scss */ $primary-color: #f40; :export { primaryColor: $primary-color; }

1
2
3
4
5
6
/* config.scss */
$primary-color: #f40;
 
:export {
  primaryColor: $primary-color;
}

 

JavaScript

/* app.js */ import style from 'config.scss'; // 会输出 #F40 console.log(style.primaryColor);

1
2
3
4
5
/* app.js */
import style from 'config.scss';
 
// 会输出 #F40
console.log(style.primaryColor);

CSS Modules介绍

CSS Modules是什么东西呢?首先,让我们从官方文档入手:
GitHub – css-modules/css-modules: Documentation about css-modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. CSS模块就是所有的类名都只有局部作用域的CSS文件。

所以CSS Modules既不是官方标准,也不是浏览器的特性,而是在构建步骤(例如使用Webpack或Browserify)中对CSS类名选择器限定作用域的一种方式(通过hash实现类似于命名空间的方法)。

It doesn’t really matter in the end (although shorter class names mean shorter stylesheets) because the point is that they are dynamically generated, unique, and mapped to the correct styles.在使用CSS模块时,类名是动态生成的,唯一的,并准确对应到源文件中的各个类的样式。

这也是实现样式作用域的原理。它们被限定在特定的模板里。例如我们在buttons.js里引入buttons.css文件,并使用.btn的样式,在其他组件里是不会被.btn影响的,除非它也引入了buttons.css.

可我们是出于什么目的把CSS和HTML文件搞得这么零碎呢?我们为什么要使用CSS模块呢?

第1步:默认局部作用域

在css模块中,每一个文件都是独立编译的,因此你可以使用一些CSS短命名-不用担心命名冲突。下面看一下,提交按钮的4种状态的例

金沙棋牌官方平台 5

CSS Modules 使用技巧

CSS Modules 是对现有的 CSS 做减法。为了追求**简单可控**,作者建议遵循如下原则:

  • 不使用选择器,只使用 class 名来定义样式
  • 不层叠多个 class,只使用一个 class 把所有样式定义好
  • 不嵌套
  • 使用 composes 组合来实现复用

上面两条原则相当于削弱了样式中最灵活的部分,初使用者很难接受。第一条实践起来难度不大,但第二条如果模块状态过多时,class 数量将成倍上升。

一定要知道,上面之所以称为建议,是因为 CSS Modules 并不强制你一定要这么做。听起来有些矛盾,由于多数 CSS 项目存在深厚的历史遗留问题,过多的限制就意味着增加迁移成本和与外部合作的成本。初期使用中肯定需要一些折衷。幸运的是,CSS Modules 这点做的很好:

1. 如果我对一个元素使用多个 class 呢?

没问题,样式照样生效。

2. 如何我在一个 style 文件中使用同名 class 呢?

没问题,这些同名 class 编译后虽然可能是随机码,但仍是同名的。

3. 如果我在 style 文件中使用了 id 选择器,伪类,标签选择器等呢?

没问题,所有这些选择器将不被转换,原封不动的出现在编译后的 css 中。也就是说 CSS Modules 只会转换 class 名相关样式。

但注意,上面 3 个“如果”尽量不要发生

为什么我们需要CSS模块化

常规的CSS书写方法

用Suit或BEM命名、一些CSS样式、一段html。代码如下:

css代码段:

/* components/submit-button.css */

.Button { /* all styles for Normal */ }

.Button--disabled { /* overrides for Disabled */ }

.Button--error { /* overrides for Error */ }

.Button--in-progress { /* overrides for In Progress */

html代码段:

<button class="Button Button--in-progress">Processing...</button>

上面代码运行不错,我们有4种状态的类名,BEM命名,避免了使用嵌套选择器。使用大写字母开头的单词Button作为选择器,避免与之前或引用样式的类名冲突。并且我们使用--modifier语法来消除基础样式。

到现在为止,这都是一段不错的可维护的代码。但也引入了严格的命名规范。但这也是能用标准CSS,做到的最好的方式了。

CSS Modules 结合 React 实践

className 处直接使用 css 中 class 名即可。

JavaScript

.root {} .confirm {} .disabledConfirm {}

1
2
3
.root {}
.confirm {}
.disabledConfirm {}

import classNames from 'classnames'; import styles from './dialog.css'; export default class Dialog extends React.Component { render() { const cx = classNames({ confirm: !this.state.disabled, disabledConfirm: this.state.disabled }); return <div className={styles.root}> <a className={styles.disabledConfirm}>Confirm</a> ... </div> } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import classNames from 'classnames';
import styles from './dialog.css';
 
export default class Dialog extends React.Component {
  render() {
    const cx = classNames({
      confirm: !this.state.disabled,
      disabledConfirm: this.state.disabled
    });
 
    return <div className={styles.root}>
      <a className={styles.disabledConfirm}>Confirm</a>
      ...
    </div>
  }
}

注意,一般把组件最外层节点对应的 class 名称为 root。这里使用了 [classnames](https://www.npmjs.com/package/classnames) 库来操作 class 名。

如果你不想频繁的输入 styles.**,可以试一下 [react-css-modules](gajus/react-css-modules · GitHub),它通过高阶函数的形式来避免重复输入 styles.**

CSS Modules 结合历史遗留项目实践

好的技术方案除了功能强大炫酷,还要能做到现有项目能平滑迁移。CSS Modules 在这一点上表现的非常灵活。

外部如何覆盖局部样式

当生成混淆的 class 名后,可以解决命名冲突,但因为无法预知最终 class 名,不能通过一般选择器覆盖。我们现在项目中的实践是可以给组件关键节点加上 data-role 属性,然后通过属性选择器来覆盖样式。

// dialog.js return <div className={styles.root} data-role='dialog-root'> <a className={styles.disabledConfirm} data-role='dialog-confirm-btn'>Confirm</a> ... </div>

1
2
3
4
5
// dialog.js
  return <div className={styles.root} data-role='dialog-root'>
      <a className={styles.disabledConfirm} data-role='dialog-confirm-btn'>Confirm</a>
      ...
  </div>

 

JavaScript

/* dialog.css */ [data-role="dialog-root"] { // override style }

1
2
3
4
/* dialog.css */
[data-role="dialog-root"] {
  // override style
}

因为 CSS Modules 只会转变类选择器,所以这里的属性选择器不需要添加 :global

如何与全局样式共存

前端项目不可避免会引入 normalize.css 或其它一类全局 css 文件。使用 Webpack 可以让全局样式和 CSS Modules 的局部样式和谐共存。下面是我们项目中使用的 webpack 部分配置代码:

JavaScript

著作权归作者所有。 商业转载请联系作者获得授权,非商业转载请注明出处。 作者:camsong 链接: 来源:知乎 // webpack.config.js 局部 module: { loaders: [{ test: /.jsx?$/, loader: 'babel' }, { test: /.scss$/, exclude: path.resolve(__dirname, 'src/styles'), loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true' }, { test: /.scss$/, include: path.resolve(__dirname, 'src/styles'), loader: 'style!css!sass?sourceMap=true' }] }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:camsong
链接:http://zhuanlan.zhihu.com/purerender/20495964
来源:知乎
 
// webpack.config.js 局部
module: {
  loaders: [{
    test: /.jsx?$/,
    loader: 'babel'
  }, {
    test: /.scss$/,
    exclude: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true'
  }, {
    test: /.scss$/,
    include: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css!sass?sourceMap=true'
  }]
}

JavaScript

/* src/app.js */ import './styles/app.scss'; import Component from './view/Component' /* src/views/Component.js */ // 以下为组件相关样式 import './Component.scss';

1
2
3
4
5
6
7
/* src/app.js */
import './styles/app.scss';
import Component from './view/Component'
 
/* src/views/Component.js */
// 以下为组件相关样式
import './Component.scss';

目录结构如下:

JavaScript

src ├── app.js ├── styles │ ├── app.scss │ └── normalize.scss └── views ├── Component.js └── Component.scss

1
2
3
4
5
6
7
8
src
├── app.js
├── styles
│   ├── app.scss
│   └── normalize.scss
└── views
    ├── Component.js
    └── Component.scss

这样所有全局的样式都放到 src/styles/app.scss 中引入就可以了。其它所有目录包括 src/views 中的样式都是局部的。

CSS全局作用域问题

CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。相信写css的人都会遇到样式冲突(污染)的问题。

于是一般这么做(笔者都做过):
* class命名写长一点吧,降低冲突的几率
* 加个父元素的选择器,限制范围
* 重新命名个class吧,比较保险

所以亟待解决的问题就是css局部作用域避免全局样式冲突(污染)的问题

CSS模块书写方法

使用CSS模块,你不用担心使用一些短命名了。可以像下面这样。

/* components/submit-button.css */

.normal { /* all styles for Normal */ }

.disabled { /* all styles for Disabled */ }

.error { /* all styles for Error */ }

.inProgress { /* all styles for In Progress */

看,你不用在任何地方再去加长长的前缀。为什么可以这样做,我们可以像其它语言一样,不用在本地变量前加长长的前缀,只要把CSS对应的文件名改成submit-botton.css

这可以让在JS中使用requireimport加载的CSS模块文件,可以被编译出来。

/* components/submit-button.js */

import styles from './submit-button.css';

buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

真正在页面使用的样式名,是动态生成的唯一标识。CSS模块把文件编译成ICSS格式的文件,这种格式文件可以方便CSS和JS进行通信。当你运行程序,会得到类似下面的代码

<button class="components_submit_button__normal__abc5436"> Processing...</button>

得到类似结果,说明运行成功~

总结

CSS Modules 很好的解决了 CSS 目前面临的模块化难题。支持与 Sass/Less/PostCSS 等搭配使用,能充分利用现有技术积累。同时也能和全局样式灵活搭配,便于项目中逐步迁移至 CSS Modules。CSS Modules 的实现也属轻量级,未来有标准解决方案后可以低成本迁移。如果你的产品中正好遇到类似问题,非常值得一试。

1 赞 2 收藏 评论

金沙棋牌官方平台 6

JS CSS无法共享变量

复杂组件要使用 JS 和 CSS 来共同处理样式,就会造成有些变量在 JS 和 CSS 中冗余,CSS预处理器/后处理器 等都不提供跨 JS 和 CSS 共享变量这种能力。

命名约定

还是拿按钮的例子来说

/* components/submit-button.css */

.normal { /* all styles for Normal */ }

.disabled { /* all styles for Disabled */ }

.error { /* all styles for Error */ }

.inProgress { /* all styles for In Progress */

所有类名都是独立的,不是一个是基类名,其它的用来修改。在CSS模块中,所有类必须包括所有的属性和样式。这让你在JS中使用类名时有很大的不同。

/* 不要像这样 */

`class=${[styles.normal, styles['in-progress']].join(" ")}`

/* 不同之处是使用单独的类名 */

`class=${styles['in-progress']}`

/* 最好使用驼峰式 */

`class=${styles.inProgress}`

当然,如果你是按照代码量来收钱的,你可以按照你的方式继续。

健壮并且扩展方便的CSS

作为有追求的工程师,编写健壮并且扩展方便的CSS一直是我们的目标。那么如何定义健壮并且扩展方便?有三个要点:

  • 面向组件 – 处理 UI 复杂性的最佳实践就是将 UI 分割成一个个的小组件 Locality_of_reference 。如果你正在使用一个合理的框架,JavaScript 方面就将原生支持(组件化)。举个例子,React 就鼓励高度组件化和分割。我们希望有一个 CSS 架构去匹配。
  • 沙箱化(Sandboxed) – 如果一个组件的样式会对其他组件产生不必要以及意想不到的影响,那么将 UI 分割成组件并没有什么用。就这方面而言,CSS的全局作用域会给你造成负担。
  • 方便 – 我们想要所有好的东西,并且不想产生更多的工作。也就是说,我们不想因为采用这个架构而让我们的开发者体验变得更糟。可能的话,我们想开发者体验变得更好。

一个React例子

这里不是关于React特有的CSS模块。但React提供了,在使用CSS模块时,特别优秀的体验。下面做一个复杂点的例子。

/* components/submit-button.jsx */

import { Component } from 'react';

import styles from './submit-button.css';

export default class SubmitButton extends Component {

render() {

let className, text = "Submit"

if (this.props.store.submissionInProgress) {

className = styles.inProgress text = "Processing..."

} else if (this.props.store.errorOccurred) {

className = styles.error

} else if (!this.props.form.valid) {

className = styles.disabled

} else {

className = styles.normal

}

return <button className={className}>{text}</button>

}

}

你可以使用你的样式,不用再担心全局冲突,让你可以专注于组件开发,而不是在写样式上。一旦离开之前的频繁在CSS,js之间切换方式,你就再也不想回去了。

但这只是开始,当你考虑样式合并时,CSS模块又没法使用了。

CSS模块化方案分类

CSS 模块化的解决方案有很多,但主要有三类。

第2步 一切皆为组件

前面提到CSS模块需要每种状态都包含所有所需的样式。

这里假设你需要多个状态,我们对比一下CSS模块和BEM命名。

/* BEM Style */

innerHTML = `<button class="Button Button--in-progress">`

/* CSS Modules */

innerHTML = `<button class="${styles.inProgress}">`

等一下,如何在所有状态共享样式呢?答案是CSS模块的最有力工具-组件

.common { /* all the common styles you want */ }

.normal { composes: common; /* anything that only applies to Normal */ }

.disabled { composes: common; /* anything that only applies to Disabled */ }

.error { composes: common; /* anything that only applies to Error */ }

.inProgress { composes: common; /* anything that only applies to In Progress */ }

关键词composes指出.normal包含.common中的样式,就像sass里的@extend关键词一样。sass是通过重写css选择器来实现的。css模块则是通过改变js中使用的类名来实现。

CSS 命名约定

规范化CSS的模块化解决方案(比如BEM BEM — Block Element Modifier,OOCSS,AMCSS,SMACSS,SUITCSS)
但存在以下问题:
* JS CSS之间依然没有打通变量和选择器等
* 复杂的命名

SASS:

使用前面的BEM例子,使用一些SASS的@extend

.Button--common { /* font-sizes, padding, border-radius */ }

.Button--normal { @extends .Button--common; /* blue color, light blue background */}

.Button--error { @extends .Button--common; /* red color, light red background */}

这将编译为

.Button--common, .Button--normal, .Button--error { /* font-sizes, padding, border-radius */ }

.Button--normal { /* blue color, light blue background */ }

.Button--error { /* red color, light red background */ }

你只需要在你的标签上引用一个类名,可以得到通用的和独有的样式。功能很强大,但你必须知道,这也存在着特殊情况和陷阱。Hugo Giraudel 汇总了一些问题,想了解更多,请点击《为什么你应该避免使用SASS的@extend》

CSS in JS

彻底抛弃 CSS,用 JavaScript 写 CSS 规则,并内联样式。 React: CSS in JS // Speaker Deck。Radium,react-style 属于这一类。但存在以下问题:
* 无法使用伪类,媒体查询等
* 样式代码也会出现大量重复。
* 不能利用成熟的 CSS 预处理器(或后处理器)

使用CSS模块

composes关键词和@extend使用方法类似,但工作方式是不同的。看个例子

.common { /* font-sizes, padding, border-radius */ }

.normal { composes: common; /* blue color, light blue background */ }

.error { composes: common; /* red color, light red background */ }

在浏览器中将会被编译为

.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }

.components_submit_button__normal__def6547 { /* blue color, light blue background */ }

.components_submit_button__error__1638bcd { /* red color, light red background */ }

在js代码中,import styles from "./submit-button.css"将得到

styles: {
common: "components_submit_button__common__abc5436",
normal: "components_submit_button__common__abc5436 components_submit_button__normal__def6547", error: "components_submit_button__common__abc5436 components_submit_button__error__1638bcd"
}

还是使用styles.normalstryles.error,在DOM中将被渲染为多个类名

<button class="components_submit_button__common__abc5436 components_submit_button__normal__def6547"> Submit</button>

这就是composes的功能,你可以合并多个样式,但不用去修改你的JS代码,也不会重写你的CSS选择器。

使用JS 来管理样式模块

使用JS编译原生的CSS文件,使其具备模块化的能力,代表是 CSS Modules GitHub – css-modules/css-modules: Documentation about css-modules 。

CSS Modules 能最大化地结合现有 CSS 生态(预处理器/后处理器等)和 JS 模块化能力,几乎零学习成本。只要你使用 Webpack,可以在任何项目中使用。是笔者认为目前最好的 CSS 模块化解决方案。

第3步.文件间共享代码

使用SASS或LESS工作,通过@import来引用同一个工作空间的文件。你可以声明变量,函数,并在其它文件中使用。很不错的方法,但在各个不同的项目中,变量命名有可能冲突。那么你就得重构你的代码,编写如variables.scsssettings.scss,你也不清楚哪些组件依赖于哪些个变量了。你的settings文件会变得很大。

也有更好的解决方案(《使用Webpack构建更小巧的CSS》),但由于SASS的全局属性,还是有很大的限制。

CSS模块一次只运行一个单独的文件,因此不会污染全局作用域。js代码用使用importrequire来引用依赖,CSS模块使用compose从另一个文件引用样式。

/* colors.css */

.primary { color: #720; }

.secondary { color: #777; }/* other helper classes... */

/* submit-button.css */

.common { /* font-sizes, padding, border-radius */ }

.normal { composes: common; composes: primary from "../shared/colors.css"; }

使用组件,我们可以像引用本地类名一样,引用colors.css文件的类。而且,组件变化的类名在输出时会被改变,但CSS文件本身并不变化,composes块也会在生成浏览器端CSS之前被去除。

/* colors.css */
.shared_colors__primary__fca929 { color: #720; }
.shared_colors__secondary__acf292 { color: #777; }

/* submit-button.css */
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 {}

 

<button class="shared_colors__primary__fca929 components_submit_button__common__abc5436 components_submit_button__normal__def6547"> Submit </button>

 

实际上,在浏览器端,normal没有自身的样式。这是好事情,你可以添加新的语义化的对象,但不用去添加CSS样式。我们还可以做得更多一点,

在全站开发中增加类名和视觉的一致性,在浏览器端减少样式代码的大小。

旁注:可以使用csso检测并移除空类。

CSS Modules 使用教程

第4步:单一职责模块

组件的强大之处在于描述一个元素是什么,而不修饰它的样式。它以一种不同的方式来映射页面实体(元素)和样式实体(样式规则)。

看一个旧的CSS例子

.some_element { font-size: 1.5rem; color: rgba(0,0,0,0); padding: 0.5rem; box-shadow: 0 0 4px -2px; }

一个元素,一些样式,很简单。尽管这样,还是存在一些问题:color,font-size,box-shadow,padding,这些都在这里指定了,但无法在其它地方使用。

我们用SASS重构一下。

$large-font-size: 1.5rem;
$dark-text: rgba(0,0,0,0);
$padding-normal: 0.5rem;
@mixin subtle-shadow { box-shadow: 0 0 4px -2px; }
.some_element {
@include subtle-shadow;
font-size: $large-font-size;
color: $dark-text;
padding: $padding-normal;
}

比旧的CSS样式有很大的改进,我们只是定义了很少的一部分。事实上像$large-font-size是排版,$padding-normal是布局,这些都仅仅用名字表达含义,不会在任何地方运行。如果要声明一个box-shadow变量,但它并不能表达自身含义,这时就必须使用@mixin@extend了。

启用 CSS Modules

JavaScript

// webpack.config.js css?modules&localIdentName=[name]__[local]-[hash:base64:5]

1
2
// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]

加上 modules 即为启用,localIdentName 是设置生成样式的命名规则。

CSS

/* components/Button.css */ .normal { /* normal 相关的所有样式 */ }

1
2
/* components/Button.css */
.normal { /* normal 相关的所有样式 */ }

JavaScript

// components/Button.js import styles from './Button.css'; console.log(styles); buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

1
2
3
4
// components/Button.js
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

生成的 HTML 是

XHTML

<button class="button--normal-abc53">Submit</button>

1
<button class="button--normal-abc53">Submit</button>

注意到 button--normal-abc53 是 CSS Modules 按照 localIdentName 自动生成的 class 名。其中的abc53 是按照给定算法生成的序列码。经过这样混淆处理后,class 名基本就是唯一的,大大降低了项目中样式覆盖的几率。同时在生产环境下修改规则,生成更短的 class 名,可以提高 CSS 的压缩率。

上例中 console 打印的结果是:

CSS

Object {   normal: 'button--normal-abc53',   disabled: 'button--disabled-def886', }

1
2
3
4
Object {
  normal: 'button--normal-abc53',
  disabled: 'button--disabled-def886',
}

CSS Modules 对 CSS 中的 class 名都做了处理,使用对象来保存原 class 和混淆后 class 的对应关系。

通过这些简单的处理,CSS Modules 实现了以下几点:
* 所有样式都是局部作用域 的,解决了全局污染问题
* class 名生成规则配置灵活,可以此来压缩 class 名
* 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
* 依然是 CSS,几乎 0 学习成本

使用CSS模块

通过使用组件,我们可以在组件中,注释写明哪些可以重复使用的类名。

.element {
composes: large from "./typography.css";
composes: dark-text from "./colors.css";
composes: padding-all-medium from "./layout.css";
composes: subtle-shadow from "./effect.css";
}

使用文件系统,而不是命名空间,来划分不同用途的样式。自然会出现引用多个单一用途的文件。

如果你想从一个文件中引用多个类,这里有一个简便的方法:

/* this short hand: */
.element {
composes: padding-large margin-small from "./layout.css";
}
/* is equivalent to: */
.element {
composes: padding-large from "./layout.css";
composes: margin-small from "./layout.css";
}

使你在网站开发上,每一种视觉对应一个类名。用上面的方式,来开发你的网站,变为一种可能。

.article {
composes: flex vertical centered from "./layout.css";
}
.masthead {
composes: serif bold 48pt centered from "./typography.css";
composes: paragraph-margin-below from "./layout.css";
}
.body {
composes: max720 paragraph-margin-below from "layout.css";
composes: sans light paragraph-line-height from "./typography.css";
}

这是一种我有兴趣进一步探索的技术。在我看来,它结合了像Tachyons的原子CSS技术,像Semantic UI样式类名的可读性,单一职责等优势。

但CSS模块的故事才刚刚开始,希望你能去在现在或将来使用它,并传播它。

CSS Modules 在React中的实践

那么我们在React中怎么使用?

上手

通过使用CSS模块,希望能帮助你和你的团队,即可以交流当前的CSS知识和产品,又可以更舒服,更高效地完成工作。我们已经尽可能保持语法的简单,并写了一些例子,当你可以使用这些例子里的代码时,你就可以使用它进行工作了。这里有一些关于Webpack,JSPM和Browseriry项目的DEMO,希望对你有所帮助。我们一直看有哪些新的环境可以运行CSS模块:正在适配服务器端NODEJS和Rails。

为了使事情更简单,这里做了一个Plunkr,可以直接动手,不用安装。开始吧

 金沙棋牌官方平台 7

如果你准备使用了,可以看一看CSS模块源码,如果有什么问题,可以在issue里进行讨论。CSS模块组,规模小,无法涵盖所有的应用场景。

期待你们的讨论。

祝:写样式开心。

原文:CSS Modules

原文链接:

手动引用解决

className 处直接使用 css 中 class 名即可。

JavaScript

import React from 'react'; import styles from './table.css';   export default class Table extends React.Component {     render () {         return <div className={styles.table}>             <div className={styles.row}>             </div>         </div>;     } }

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import styles from './table.css';
 
export default class Table extends React.Component {
    render () {
        return <div className={styles.table}>
            <div className={styles.row}>
            </div>
        </div>;
    }
}

渲染出来的组件出来

XHTML

<div class="table__table___32osj">     <div class="table__row___2w27N">     </div> </div>

1
2
3
4
<div class="table__table___32osj">
    <div class="table__row___2w27N">
    </div>
</div>

react-css-modules

如果你不想频繁的输入 styles.**,有一个 GitHub – gajus/react-css-modules: Seamless mapping of class names to CSS modules inside of React components.,它通过高阶函数的形式来生成className,不过不推荐使用,后文会提到。

API也很简单,给组件外包一个CSSModules即可。

JavaScript

import React from 'react'; import CSSModules from 'react-css-modules'; import styles from './table.css';   class Table extends React.Component {     render () {         return <div styleName='table'>         </div>;     } }   export default CSSModules(Table, styles);

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
 
class Table extends React.Component {
    render () {
        return <div styleName='table'>
        </div>;
    }
}
 
export default CSSModules(Table, styles);

不过这样我们可以看到,它是需要运行时的依赖,而且需要在运行时才获取className,性能损耗大,那么有没有方便又接近无损的方法呢?答案是有的,使用babel插件babel-plugin-react-css-modulesGitHub – gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution. 把className获取前置到编译阶段。

babel-plugin-react-css-modules

babel-plugin-react-css-modules 可以实现使用styleName属性自动加载CSS模块。我们通过该babel插件来进行语法树解析并最终生成className

来看看组件的写法,现在你只需要把className换成styleName即可获得CSS局部作用域的能力了,是不是非常简单。

JavaScript

import React from 'react'; import styles from './table.css';   class Table extends React.Component {     render () {         return <div styleName='table'>         </div>;     } }   export default Table;

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import styles from './table.css';
 
class Table extends React.Component {
    render () {
        return <div styleName='table'>
        </div>;
    }
}
 
export default Table;

工作原理

那么该babel插件是怎么工作的呢?让我们从官方文档入手:

GitHub – gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution.

笔者不才 ,稍作翻译如下:
1. 构建每个文件的所有样式表导入的索引(导入具有.css.scss扩展名的文件)。

  1. 使用postcss 解析匹配到的css文件
  2. 遍历所有 JSX 元素声明
  3. styleName 属性解析成匿名和命名的局部css模块引用
  4. 查找与CSS模块引用相匹配的CSS类名称:
    * 如果styleName的值是一个字符串字面值,生成一个字符串字面值。
    * 如果是JSXExpressionContainer,在运行时使用helper函数来构建如果styleName的值是一个jSXExpressionContainer, 使用辅助函数([getClassName]在运行时构造className值。
  5. 从元素上移除styleName属性。
    7. 将生成的className添加到现有的className值中(如果不存在则创建className属性)。

使用实例

在成熟的项目中,一般都会用到CSS预处理器或者后处理器。

这里以使用了stylusCSS预处理器为例子,我们来看下如何使用。

  • 安装依赖

Shell

npm install -save-dev sugerss babel-plugin-react-css-modules

1
npm install -save-dev sugerss babel-plugin-react-css-modules
  • 编写Webpack配置
JavaScript

// webpack.config.js module: {   loaders: [{     test: /\.js?$/,
    loader: [['babel-plugin-react-css-modules',{
          generateScopedName:'[name]__[local]',
          filetypes: {               ".styl": "sugerss"            }
     }]]   }, {     test: /\.module.styl$/,     loader:
'style!css?modules&localIdentName=[name]__[local]!styl?sourceMap=true'
  }, {     test: /\.styl$/,     loader:
'style!css!styl?sourceMap=true'   }] }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-15">
15
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-16">
16
</div>
<div class="crayon-num" data-line="crayon-5b8f6729d9ed4116610999-17">
17
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6729d9ed4116610999-18">
18
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6729d9ed4116610999-1" class="crayon-line">
// webpack.config.js
</div>
<div id="crayon-5b8f6729d9ed4116610999-2" class="crayon-line crayon-striped-line">
module: {
</div>
<div id="crayon-5b8f6729d9ed4116610999-3" class="crayon-line">
  loaders: [{
</div>
<div id="crayon-5b8f6729d9ed4116610999-4" class="crayon-line crayon-striped-line">
    test: /.js?$/,
</div>
<div id="crayon-5b8f6729d9ed4116610999-5" class="crayon-line">
    loader: [['babel-plugin-react-css-modules',{
</div>
<div id="crayon-5b8f6729d9ed4116610999-6" class="crayon-line crayon-striped-line">
          generateScopedName:'[name]__[local]',
</div>
<div id="crayon-5b8f6729d9ed4116610999-7" class="crayon-line">
          filetypes: {
</div>
<div id="crayon-5b8f6729d9ed4116610999-8" class="crayon-line crayon-striped-line">
              &quot;.styl&quot;: &quot;sugerss&quot;
</div>
<div id="crayon-5b8f6729d9ed4116610999-9" class="crayon-line">
           }
</div>
<div id="crayon-5b8f6729d9ed4116610999-10" class="crayon-line crayon-striped-line">
     }]]
</div>
<div id="crayon-5b8f6729d9ed4116610999-11" class="crayon-line">
  }, {
</div>
<div id="crayon-5b8f6729d9ed4116610999-12" class="crayon-line crayon-striped-line">
    test: /.module.styl$/,
</div>
<div id="crayon-5b8f6729d9ed4116610999-13" class="crayon-line">
    loader: 'style!css?modules&amp;localIdentName=[name]__[local]!styl?sourceMap=true'
</div>
<div id="crayon-5b8f6729d9ed4116610999-14" class="crayon-line crayon-striped-line">
  }, {
</div>
<div id="crayon-5b8f6729d9ed4116610999-15" class="crayon-line">
    test: /.styl$/,
</div>
<div id="crayon-5b8f6729d9ed4116610999-16" class="crayon-line crayon-striped-line">
    loader: 'style!css!styl?sourceMap=true'
</div>
<div id="crayon-5b8f6729d9ed4116610999-17" class="crayon-line">
  }]
</div>
<div id="crayon-5b8f6729d9ed4116610999-18" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>
  • 组件写法

JavaScript

import React from 'react'; import './table.module.styl';   class Table extends React.Component {     render () {         return <div styleName='table'>         </div>;     } }   export default Table;

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import './table.module.styl';
 
class Table extends React.Component {
    render () {
        return <div styleName='table'>
        </div>;
    }
}
 
export default Table;

如上,你可以通过配置Webpack中module.loaders的test路径Webpack-module-loaders-configuration,来区分样式文件是否需要CSS模块化。
搭配sugerss这个postcss插件作为stylus的语法加载器,来支持babel插件babel-plugin-react-css-modules的语法解析。

最后我们回过头来看下,我们React组件只需要把className换成styleName,搭配以上构建配置,即可实现CSS模块化 。

最后

CSS Modules 很好的解决了 CSS 目前面临的模块化难题。支持与 CSS处理器搭配使用,能充分利用现有技术积累。如果你的产品中正好遇到类似问题,非常值得一试。

希望大家都能写出健壮并且可扩展的CSS,以上。

1 赞 2 收藏 评论

金沙棋牌官方平台 8

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:未来的编码方式

关键词: