CSS-in-JS 简单了解

653 阅读4分钟

什么是 CSS-in-JS

js 一样地使用 css 。表现有2种:

  1. css 写法维持不变,但 js 可引用,可调用(css Module);
  2. 使用 js 来书写 css ,更灵活(JSS / styled-components)。

为什么要用 CSS-in-JS

为什么要用 CSS-in-JS ?大概是它可以解决 CSS 的一些痛点和带来了一些明显的爽点吧。

  1. 作用范围

    一般来说,样式的作用范围是全局的,但我们是模块化,组件化开发,希望是样式在当前模块/组件范围内有效,而不会影响到其他模块/组件/全局的样式。

    Vue 中使用 scoped 来让当前组件的样式不会影响到其他范围。

    平时我们也可以使用一个类选择器/ID选择器将当前的样式包裹在内,使样式有所局限。

  2. 潜在依赖

    同一个元素,可能叠加了多个样式规则。一个样式规则的定义,可能包含着多个元素的样式规则。

    What actually is CSS-in-JS?

    1. A CSS rule includes multiple selectors to target different HTML elements.

    2. Multiple class names or other attributes are applied to HTML elements, causing them to be targeted by multiple CSS rules.

  3. 无用代码

    css 可复用,但在复用过程中,某些样式可能并不是目标元素想要的/有效果的。此时,这些多余的/无效的代码,就是无用代码。

  4. 加载顺序造成影响

    页面A和页面B 都有一个相同的类选择器,但是样式设置是不一样的。先进入页面A,cssA文件下载了,作用了,然后进入页面B,cssB 文件下载了,也作用了。此时,页面是正常的。但是返回页面A的时候,发现页面A的样式出现了问题--因为此时 cssB 里定义的样式覆盖了 cssA 里定义的了。这个就是加载顺序造成的样式影响。

  5. 1对多的依赖

    同一个样式规则,可能应用在多个元素上。如果想改动这个样式规则,代价就非常大了。

  6. 可维护性更高

    样式规则定义的颗粒度小(元素),作用范围小(组件),且使用 js 来编写/引入。层级关系比 css 的写法更明显。

  7. 灵活性更高

    使用 js 编写,可引入变量,使用方法,根据当时环境来执行,灵活度可想而知。

以上,都可能是你想要用 CSS-in-JS 的原因。总结一下,就是:使样式规则定义颗粒度更细更专一;提倡组件复用(即元素+样式)而不是样式复用;更好的可维护性和灵活性。

怎么使用

不同的库,使用方法不一致。如:

css Module

仍需要 .css 文件,且 CSS-in-JS 的味道很淡。

// 编译前
// app.css
.txt {
  color: '#f00';
}
// app.js
import React from 'react';
import style from './app.css';
export default () => (
	<p className={style.txt}>hello</p>
)

// 编译工具编译后
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
  Hello World
</h1>
// app.css
._3zyde4l1yATCOkgn-DBWEL {
  color: red;
}

jss

干掉了 .css 文件,使用 JSON对象 来表示。可抽离,但抽离后,就仿 佛是 .css 转变为 .js

// 代码来自 https://cssinjs.org/?v=v10.5.0
import React from 'react'
import {render} from 'react-dom'
import {createUseStyles} from 'react-jss'

const useStyles = createUseStyles({
  myButton: {
    color: 'green',
    margin: {
      // jss-expand gives more readable syntax
      top: 5, // jss-default-unit makes this 5px
      right: 0,
      bottom: 0,
      left: '1rem'
    },
    '& span': {
      // jss-nested applies this to a child span
      fontWeight: 'bold' // jss-camel-case turns this into 'font-weight'
    }
  },
  myLabel: {
    fontStyle: 'italic'
  }
})

const Button = ({children}) => {
  const classes = useStyles()
  return (
    <button className={classes.myButton}>
      <span className={classes.myLabel}>{children}</span>
    </button>
  )
}

const App = () => <Button>Submit</Button>

render(<App />, document.getElementById('root'))

styled-components

最受欢迎的 CSS-in-JS 库。使用方法别具一格。

const Button = styled.button`
	color: '#f00'
`;

function Temp() {
  return <Button />;
}

styled-components 使用方法实现原理

先看下 styled-components 的简单的使用:

const Button = styled.button`
	color: '#f00'
`;

这里的诀窍就是:hello('world') == hello`world` (只是调用效果类似,并不全等)。

所以

const Button = styled.button`
	color: '#f00'
`;
// 等同于
const Button = styled.button(["color: '#f00'", ["color: '#f00'"]]);

为什么 hello('world') == hello`world` 呢?

从 AST 上看,**hello`world** 也是一个表达式(TaggedTemplateExpression`)来的。所以无怪这样怪异的语句也可以执行了。

当然, styled-components 的使用方法实现原理还有很多其他的处理,比如:

const Button = styled.button`
	color: {props => props.color ? props.color : '#f00'}
`;

我们现在只是弄懂了:为什么 styled-components 可以使用类似 styled.button`...` 这样的调用方式来调用而已。

还有很多细节可以继续了解。

参考

  1. JSS cssinjs.org/?v=v10.5.0
  2. styled-components styled-components.com/docs/basics
  3. TaggedTemplateExpression github.com/estree/estr…
  4. astexplorer.net/
  5. What actually is CSS-in-JS? medium.com/dailyjs/wha…