CSS in JS 简介

作者: 阮一峰

日期: 2017年4月 5日

1、

以前,网页开发有一个原则,叫做"关注点分离"(separation of concerns)。

它的意思是,各种技术只负责自己的领域,不要混合在一起,形成耦合。对于网页开发来说,主要是三种技术分离。

  • HTML 语言:负责网页的结构,又称语义层
  • CSS 语言:负责网页的样式,又称视觉层
  • JavaScript 语言:负责网页的逻辑和交互,又称逻辑层或交互层

简单说,就是一句话,不要写"行内样式"(inline style)和"行内脚本"(inline script)。比如,下面代码就很糟糕(查看完整代码)。


<h1 style="color:red;font-size:46px;"  onclick="alert('Hi')">
  Hello World
</h1>

2、

React 出现以后,这个原则不再适用了。因为,React 是组件结构,强制要求把 HTML、CSS、JavaScript 写在一起。

上面的例子使用 React 改写如下(查看完整代码)。


const style = {
  'color': 'red',
  'fontSize': '46px'
};

const clickHandler = () => alert('hi'); 

ReactDOM.render(
  <h1 style={style} onclick={clickHandler}>
     Hello, world!
  </h1>,
  document.getElementById('example')
);

上面代码在一个文件里面,封装了结构、样式和逻辑,完全违背了"关注点分离"的原则,很多人不适应。

但是,这有利于组件的隔离。每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,很方便复用。所以,随着 React 的走红和组件模式深入人心,这种"关注点混合"的新写法逐渐成为主流。

3、

表面上,React 的写法是 HTML、CSS、JavaScript 混合在一起。但是,实际上不是。现在其实是用 JavaScript 在写 HTML 和 CSS。

React 在 JavaScript 里面实现了对 HTML 和 CSS 的封装,我们通过封装去操作 HTML 和 CSS。也就是说,网页的结构和样式都通过 JavaScript 操作。

4、

React 对 HTML 的封装是 JSX 语言 ,这个在各种 React 教程都有详细介绍,本文不再涉及了,下面来看 React 对 CSS 的封装。

React 对 CSS 封装非常简单,就是沿用了 DOM 的 style 属性对象,这个在前面已经看到过了。


const style = {
  'color': 'red',
  'fontSize': '46px'
};

上面代码中,CSS 的font-size属性要写成fontSize,这是 JavaScript 操作 CSS 属性的约定

由于 CSS 的封装非常弱,导致了一系列的第三方库,用来加强 React 的 CSS 操作。它们统称为 CSS in JS,意思就是使用 JS 语言写 CSS。根据不完全统计,各种 CSS in JS 的库至少有47种。老实说,现在也看不出来,哪一个库会变成主流。

你可能会问,它们与"CSS 预处理器"(比如 Less 和 Sass,包括 PostCSS)有什么区别?回答是 CSS in JS 使用 JavaScript 的语法,是 JavaScript 脚本的一部分,不用从头学习一套专用的 API,也不会多一道编译步骤。

5、

上周,我看到一个新的 CSS in JS 库,叫做 polished.js。它将一些常用的 CSS 属性封装成函数,用起来非常方便,充分体现使用 JavaScript 语言写 CSS 的优势。

我觉得这个库很值得推荐,这篇文章的主要目的,就是想从这个库来看怎么使用 CSS in JS。

首先,加载 polished.js。


const polished = require('polished');

如果是浏览器,插入下面的脚本。


<script src="https://unpkg.com/[email protected]/dist/polished.min.js">
</script>

polished.js目前有50多个方法,比如clearfix方法用来清理浮动。


const styles = {
  ...polished.clearFix(),
};

上面代码中,clearFix就是一个普通的 JavaScript 函数,返回一个对象。


polished.clearFix()
// {
//  &::after: {
//    clear: "both",
//    content: "",
//    display: "table"
//  }
// }

"展开运算符"(...)将clearFix返回的对象展开,便于与其他 CSS 属性混合。然后,将样式对象赋给 React 组件的style属性,这个组件就能清理浮动了。


ReactDOM.render(
  <h1 style={style}>Hello, React!</h1>,
  document.getElementById('example')
);

从这个例子,大家应该能够体会polished.js的用法。

6、

下面再看几个很有用的函数。

ellipsis将超过指定长度的文本,使用省略号替代(查看完整代码)。


const styles = {
  ...polished.ellipsis('200px')
}

// 返回值
// {
//   'display': 'inline-block',
//   'max-width': '250px',
//   'overflow': 'hidden',
//   'text-overflow': 'ellipsis',
//   'white-space': 'nowrap',
//   'word-wrap': 'normal'
// }

hideText用于隐藏文本,显示图片。


const styles = {
  'background-image': 'url(logo.png)',
  ...polished.hideText(),
};

// 返回值
// {
  'background-image': 'url(logo.png)',
  'text-indent': '101%',
  'overflow': 'hidden',
  'white-space': 'nowrap',
}

hiDPI指定高分屏样式。


const styles = {
 [polished.hiDPI(1.5)]: {
   width: '200px',
 }
};

// 返回值
//'@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
// only screen and (min--moz-device-pixel-ratio: 1.5),
// only screen and (-o-min-device-pixel-ratio: 1.5/1),
// only screen and (min-resolution: 144dpi),
// only screen and (min-resolution: 1.5dppx)': {
//  'width': '200px',
//}

retinaImage为高分屏和低分屏设置不同的背景图。


const styles = {
 ...polished.retinaImage('my-img')
};

// 返回值
//   backgroundImage: 'url(my-img.png)',
//  '@media only screen and (-webkit-min-device-pixel-ratio: 1.3),
//   only screen and (min--moz-device-pixel-ratio: 1.3),
//   only screen and (-o-min-device-pixel-ratio: 1.3/1),
//   only screen and (min-resolution: 144dpi),
//   only screen and (min-resolution: 1.5dppx)': {
//    backgroundImage: 'url(my-img_2x.png)',
//  }

7、

polished.js提供的其他方法如下,详细用法请参考文档

  • normalize():样式表初始化
  • placeholder():对 placeholder 伪元素设置样式
  • selection():对 selection 伪元素设置样式
  • darken():调节颜色深浅
  • lighten():调节颜色深浅
  • desaturate():降低颜色的饱和度
  • saturate():增加颜色的饱和度
  • opacify():调节透明度
  • complement():返回互补色
  • grayscale():将一个颜色转为灰度
  • rgb():指定红、绿、蓝三个值,返回一个颜色
  • rgba():指定红、绿、蓝和透明度四个值,返回一个颜色
  • hsl():指定色调、饱和度和亮度三个值,返回一个颜色
  • hsla():指定色调、饱和度、亮度和透明度三个值,返回一个颜色
  • mix():混合两种颜色
  • em():将像素转为 em
  • rem():将像素转为 rem

目前,polished.js只是1.0版,以后应该会有越来越多的方法。

8、

polished.js还有一个特色:所有函数默认都是柯里化的,因此可以进行函数组合运算,定制出自己想要的函数。


import { compose } from 'ramda';
import { lighten, desaturate } from 'polished';

const tone = compose(lighten(10), desaturate(10))

上面代码使用 Ramda 函数库完成组合运算。Ramda 的用法可以参考我写的教程

(正文完)

==========

最后,发布一个活动消息。

大家知道,美国最大之一的在线教育网站优达学城(Udacity),一直赞助我的博客。他们正在国内推广深度学习课程(中英双语),有一系列的配套活动。

4月6日晚上8点,他们邀请深度好奇公司( DeeplyCurious.ai )的创始人兼 CTO 吕正东博士,举办一场知乎 Live,探讨深度学习和语言智能,感兴趣的朋友不要错过。

【主讲人】

吕正东博士,曾任职于微软亚洲研究院、华为诺亚方舟实验室等著名研究机构,长期从事机器学习及人工智能的研究,在深度学习、自然语言处理和半监督学习等领域卓有建树,是深度学习领域(尤其是自然语言处理方向)具有世界顶尖水平并享有国际声誉的科学家和技术专家。

【活动内容】

  • 深度学习在自然语言处理方面的新进展
  • 深度学习是否会主导自然语言处理
  • 自然语言处理和人工智能的下一个大事件
  • 我为什么创立深度好奇
  • 自由提问时间

【时间】

4月6日晚上8点

【网址】

知乎 Live

(完)

留言(36条)

小组件用这种方式还好,如果样式很多,这样写在一块个人认为有些不妥

css in js 实现动画非常方便。
但在使用过程中遇到很麻烦的一点是很多库都没法定义子元素的样式。

说实话看不出来优势在哪里

一时还习惯不了混合到一起的写法。简单的页面还好,页面太过复杂就不太适合了!

好多广告 ...

写起来很方便

个人觉得优势还没有凸显,在制作复杂页面时或使用CSS较多时是否会增加浏览器解析的负担,拖慢页面加载的速度?

支持ssr么?
不过我个人还是喜欢styled-components 这个库

我写react是用的style-loader!css-loader来模块化

react component 并不是html的封装,而是dom元素的数据结构的实现,正因为如此才会有独立的渲染模块,如react-native 和react-dom,负责翻译react 元素。

react的做法是不是默认程序员写所有代码?如果负责写样式的和负责写业务逻辑的不是一个人呢?写样式的也要学react?

React 还是太过麻烦,请看使用本人框架写就的组件,内容改自阮老师文中的 React 组件.

Example: {
css: "#example {color: red; font-size: 46px}",
xml: "\
hello,world
",
fun: function (sys, items, opts) {
sys.example.on(click, () => alert('hi'));
}
}

纯 JavaScript,不使用 JSX,代码是不是更清晰,而且同一套代码前后端通跑。

最近有个Algolia的“Goodbye JavaScript:Introducing CSS API Client”(JS已死,拥抱CSS)阮哥怎么看?

推荐一下styled-components

前端的智慧就集中在了来回折腾模式和框架或者工具上了??

polished.js 返回的是 "max-width" 这种形式的
直接用在 React 的 style 上面会报
`Warning: Unsupported style property max-width. Did you mean maxWidth`
有配置或者转换的方法吗?

這仍然是 SoC,只是面向換成 Component

全世界的前端人都认为前端的精髓就是“折腾”? 市面上各种框架火不过一年,然后即便是这样也还是有大量的框架开发者前赴后继,最后都淹死在茫茫的框架海洋中 ...

关注点的分离,是前端工程化的关键之一。现在主流的前端框架都采用的是组件树的思想。

引用项少龙的发言:

关注点的分离,是前端工程化的关键之一。现在主流的前端框架都采用的是组件树的思想。

因为是一帮面向对象的程序员转前端做这种一锅大杂烩的事情。即使是组件,组件内部应该有分离,这样并不影响与其它组件的独立性。

现在大型项目,已经被程序员整成流水线模式了,非常便捷快捷,一个JS做引子,路由映射组件模块,杠杠得。

css in js 感觉非常适合组件化,css原来的作用是复用class,现在复用组件就相当于复用了class,所以根本没必要按照原来的写法。上面有人说复杂的不好,复杂的应该分为几个组件写最后还是小组件组成。

第5条的清除浮动的 有一点小的瑕疵

应该修改为 style={styles}

算了吧,这样写太麻烦了
还不如自己定义好命名空间。
或者react能实现一套vue那样的scoped就好了

没有postcss支持,很难受,引工具或者自己写转换逻辑又会加重渲染负担。

假如同时用两个样式怎么办?因为不可能它本身定义的样式一定够用吧!难道要如下这样吗?
const styles = {
...polished.clearFix(),
"height":1px
};
那还不如用react本身的做法...个人看法

还是太麻烦,比较喜欢`vue`的那一套,不过期待`react`最后能把`css in js`搞成什么样子

@曲斗:

还是要从各方面考虑, elm也很不错, 但是综合其难度, 并没有人问津, 还有react虚拟dom的机制让性能大幅增加了。

那位大神知道react 如何做按需加载css啊,像 vue和angular4 一样?谢谢了。

从关注点分离到组件树的演变,组合方式也不断变化啊.

阮老师,在css-in-js写法里,我要实现响应式应该怎么实现呢?比如一行图片,每个为父容器的25%宽,但但父容器宽度小于600px,每个为父容器的50%。



试一下我们老师讲的xss攻击

引用飞硕的发言:

react的做法是不是默认程序员写所有代码?如果负责写样式的和负责写业务逻辑的不是一个人呢?写样式的也要学react?

能分得那么细?一个模块的逻辑和样式不应该是同一个人写嘛。

polished将css封装成函数确实很创新, 但随之而来的多个api的学习成本和可定制化差的缺点

为什么这个网站https://codesandbox.io好多样式都不能在浏览器上改(只有标注是style的)

2017年那么多人反对 css-in-js ,但是现在 material-ui 就把 css-in-js 运用得很好。

css-in-js 使得样式不在是预先设置,可以动态改变,自定义主题变成了修改一个 js 变量,而不再是消除旧主题的样式,引入新主题的样式,页面风格高度可定制化。自定义生成页面使用 css-in-js 做为样式支持,会使程序变得异常简单。

css-in-js 目前有两点不足:一是增加渲染负担,不过基本上是毫秒级别,其逻辑不过是遍历 css-in-js 对象然后转换为字符串插入到 style 元素内而已;二是语法提醒,这点虽然 typescript 可以一定程度上的弥补,不过比起 css、sass、less 等文件的语法提醒来说,较为鸡肋。

css-in-js 国内不温不火,我猜原因有下:国内钟爱 vue ,而 vue 相关 ui 库并没有对 css-in-js 有很好的支持;css-in-js 库太多,没有较为突出权威的库,这是因为 css-in-js 实现简单,迭代也更加迅速;css-in-js 门槛较高,学完 css 可以直接学习 sass、less 并且没有压力,但是 css-in-js 需要再学习 js,还要考虑和当前框架的兼容性,能不能很好嵌入等等问题。

引用繁星的发言:

polished将css封装成函数确实很创新, 但随之而来的多个api的学习成本和可定制化差的缺点

出现的原因也是为迎合时代的潮流

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接