打破“防自学机制”,种草这几个库了解CSS-in-JS

目录

  • 前言
  • JSS (JavaScript Style Sheets)
      1. react-jss
      1. @stylexjs/stylex
  • Styled Component
      1. styled-component
      1. @emotion/styled
  • twin.macro
    • 通过Babel宏提供简便语法糖
    • 结合 Tailwind CSS 的实用性与 CSS-in-JS 的灵活性
  • antd在CSS-in-JS中的实践
    • CSS架构模式(v4.x)
    • CSS变量方案
    • CSS-in-JS方案
  • antd-style
    • JSS + css模版字符串

前言

“CSS-in-JS是一种方案术语,方案术语的起源通常源于几个业界广泛认可的库,它们共同组成并逐渐发展出相应的方案或环境。”

简单来说 CSS-in-JS 就是将应用的CSS样式写在JavaScript文件里面,而不是独立为.css/less/scss之类的文件。
这样它们就能拥有了JS语言特性的能力:模块声明、变量定义、函数调用和条件判断等:

  • 动态生成样式、包括自动添加浏览起前缀
  • 不会出现类名重复问题
  • 可以轻松地删除无用CSS等

也许我们对术语的概念会模糊不清,不要担心,通过探索以下这些具有相似动机和思想的库,就能充分认识CSS-in-JS这个世界。

JSS (JavaScript Style Sheets)

JSS 是一类具体的 CSS-in-JS 实现库,命名灵感来自JavaScript Style Sheets(JSSS),
语法风格偏向函数式,在CSS-in-JS可以作为一个大类以区分。

1. react-jss

  1. 文档地址:https://cssinjs.org/react-jss?v=v10.10.1
  2. 样式通过 JavaScript 对象来定义,使用对象结构来写样式;
  3. 支持动态样式组件内样式隔离。🚩
  4. 样式的生成发生在运行时,灵活性高,但在性能上不如静态编译的方案;
  5. 适合复杂的、需要动态生成或修改样式的场景,但可能在某些性能敏感的场景下表现不佳。(jss系列已停止维护,不推荐使用⚠️)
import React from 'react'
import { createUseStyles } from 'react-jss'

const Button = ({children, ...props}) => {
  const class = useStyles(props);
  return (
    <button className={class.myButton}>
        <label className={class.myLabel}>{children}</label>
    </button>
  )
}

const useStyles = createUseStyles({
  myButton: {
    margin: 10,
    padding: props=>props.spacing
  },
  myLabel: props =>({
    height: props.height
  })
})
<Button height={10} spaceing={5}>Submit</Button>

上述代码将编译为 ⬇️ ⬇️ ⬇️ ;
但当heightspaceing更新时则会重新生成classname,如Button-myButton-1-25➡️Button-myButton-1-27

<button class="Button-myButton-1-25">
  <label class="Button-myLabel-1-26"> Submit </label>
</button>

<style>
.Button-myButton-1-25 {
  margin: 10px;
  padding: 5px;
}
.Button-myLabel-1-26 {
  heighte: 10px;
}
</style>

2. @stylexjs/stylex

  1. 文档地址:https://stylexjs.com/docs/learn/styling-ui/defining-styles/
  2. 通过静态提取的方式将样式转化为类名零运行时消耗。🚩
  3. 适用于渲染性能要求比较高的应用场景,特别是移动端的应用。(推荐使用✅)
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  button: {
    backgroundColor: 'lightblue',
  }
});

<button {…stylex.props(styles.button)} />
  1. 支持动态注入(将生成依赖于CSS变量的静态样式,并在运行时设置该变量的值)。
import * as stylex from "@stylexjs/stylex"
const styles = stylex.create({
  button: (height) => ({
    backgroundColor: 'lightblue',
    height
  })
})

<button {...stylex.props(styles.button(19))} />

上述代码将编译为⬇️ ⬇️ ⬇️

<button class="x1jwls1v" style="--height:19" />

<style>
  .x1jwls1v {
    background-color: lightblue;
    height: var(--height, revert);
  }
</style>

Styled Component

Styled-Component也是一类具体的 CSS-in-JS 实现库,
styled写法由 styled-component首创,使用ES6模版字符串的形式编写强调组件级别样式,在CSS-in-JS也可以作为一个大类。

1. styled-component

  1. styled方法可以在所有组件或任何第三方组件上完美运行,只要组件是通过className属性获取样式;
    🚩Note: react-native组件已经默认兼容为style

  2. 优势是:有着成熟的生态系统和社区支持,许多第三方库和组件也基于它,适合初次接触CSS-in-JS的开发者。 ✅

import { styled } from 'styled-components'

const Button = styled.button`
    color: turquoise;
 `

return (
  <Button></Button>
){

 
{/*样式最终是被编译成hash类名 ⬇️ ⬇️ ⬇️*/}
(<button {…props} className=“css-hashname”/>)

2. @emotion/styled

  1. 相对于styled-component,@emotion/styled是经过@emotion系列拆分后的体积更小
  2. SSR 方面的表现更为出色,支持静态 CSS 提取,可以减少首次加载时的样式计算。✅
  3. @emption/css首次提出了css模版字符串生成classname的写法
  • @emotion/styled
    styled-component独立包

    import styled from '@emotion/styled'
    
  • @emotion/css
    书写内联style生成className的基础包🌟

    /** @jsxImportSource @emotion/css */
    import { css } from '@emotion/css'
    
    const className = css`
      color: hotpink;
    `
    (<div className={className} />)
    
  • @emotion/react
    react项目专用

    import { jsx, css, Global, ClassNames } from '@emotion/react'
    

twin.macro

通过Babel宏提供简便语法糖

🚩twin.macro 集成了 styled-component@emotion/styledtailwindcss
(查看 package.json 依赖)

Note: nextjs14 SWC打包器不支持babel宏,不过最近修复了这个问题 https://github.com/ben-rogerson/twin.examples/tree/master/next-stitches-typescript

  1. 【 tw=“classname” 】➡️ className

    可以直接引用tailwind类名 🌟

    import tw from "twin.macro";
    
    <Tag tw={"text-red-900"}> antd tag </Tag>
    
    ⬇️ ⬇️ ⬇️ 
    
    (<Tag className="text-red-900"> antd tag </Tag>)
    
  2. 【 css={styleProps} 】➡️ className

    (<Tag css={{color: "red"}}> antd tag </Tag>)
    
    ⬇️ ⬇️ ⬇️
    
    (<Tag className="css-hashxx-Home"> antd tag </Tag>)
    

    和 【@emotion/css】 混合使用

    import tw from "twin.macro"
    import { css } from "@emotion/css";
    
    <div className="css`font-size:50px; ${tw`text-red-900`}`"></div>
    
  3. **tw【classname】 ➡️ css **🌟

    const style = tw`text-red-900`
    ⬇️ ⬇️ ⬇️
    const style = {color:”red”}
    
    (<h1 style={style}>asd</h1>)
    

结合 Tailwind CSS 的实用性与 CSS-in-JS 的灵活性

  1. 单一的styled-component会疲于手写CSS样式;
  2. 单一的tailwind样式类名会导致代码很长、阅读性差;
  3. 各取所长,即做到组件级别的样式隔离,又能快速定义样式。
import tw, { styled } from 'twin.macro'

const Input = styled.input`
  color: purple;
  ${tw`border rounded`}
  ${({ hasHover }) => hasHover && tw`hover:border-black`}
`
const Component = () => <Input hasHover />

antd在CSS-in-JS中的实践

CSS架构模式(v4.x)

在V5.0之前的组件样式设计使用CSS架构模式,便于:

  1. 修改prefixCls
<Button prefixCls="my" />
<style>
 .my-btn {}
</style>

  ⬇️ ⬇️ ⬇️
- <button class="ant-btn" />
+ <button class="my-btn" />
  1. 固定 class 便于覆盖(包括傀儡class)

    傀儡class:本身无样式,为了单纯覆盖

  2. 按需引入

    import {Button} from "antd"
    
    ⬇️ ⬇️ ⬇️
    import Button from 'antd/lib/button'
    import 'antd/es/button/style'
    

CSS变量方案

V4.17.0文档:https://4x.ant.design/docs/react/customize-theme-variable-cn (试验性)
V5.0文档:https://ant.design/docs/react/css-variables-cn (融合了CSS-in-JS能力)

CSS变量的优势:

  • 样式只生成一次
  • 动态主题只修改变量
  • 多主题只增加变量

CSS变量的劣势:

  • 浏览起兼容性差(如IE)
  • 动态修改 CSS 变量可能导致性能下降,尤其是在频繁更改时
  • 可通过cssVar配置来开启/关闭CSS变量模式

CSS-in-JS方案

在5.0正式版本中,除了保留之前的CSS架构模式外,还带入了Ant Design独特的CSS-in-JS 方案(我们称之为《组件级别的CSS-in-JS》)🚩
相关依赖库:@ant-design/cssinjs

  1. 组件为单位的缓存
    上述虽有plugin自动帮我们引入css了,但是CSS-in-JS可以更好地封装组件,
    因为样式直接写在JS里了,import一个文件即可。

  2. 支持动态主题/嵌套主题

    在V5.0中ConfigProvider组件引入了theme属性,外加每个组件都细化了足量的Design Token,带来了无与伦比的主题自定义能力。文档链接:《定制主题》

<ConfigProvider
  theme={{
    token: {
      // Seed Token,影响范围大
      colorPrimary: '#00b96b',
      borderRadius: 2,

      // 派生变量,影响范围小
      colorBgContainer: '#f6ffed',
    },
  }}
>
   <Button type="primary">Theme 2</Button>
</ConfigProvider>
  1. 针对SSR的优化
    参考代码: @ant-design/nextjs-registry

antd-style

文档链接:https://ant-design.github.io/antd-style/zh-CN/guide

antd内部使用的@ant-design/cssinjs写法极其复杂难懂,这是为了兼容历史包袱的产物,也为了换得相比 styled-component 和 emotion 都要好很多的性能。

虽然antd内部不能像JSS或者Styled Component那样的写法,因为职责和边界只在于提供高品质的基础组件;但是应用层如何使用样式方案, antd 并不限制。

🚩为了将 v5 的 token 系统的推行变得更加顺利,ant design组织还提供了一个使用antd token系统的最佳cssinjs方案:antd-style

JSS + css模版字符串

antd-style是基于emotion二次封装,提供的核心api是createStyles,所以写法上包含了JSScss模版字符串2种写法:

import { createStyles } from 'antd-style';

const useStyles = createStyles(({ token, css }) => ({
  // 支持 css object 的写法
  container: {
    backgroundColor: token.colorBgLayout,
    borderRadius: token.borderRadiusLG,
    maxWidth: 400,
    width: '100%',
    height: 180,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    marginLeft: 'auto',
    marginRight: 'auto',
  },
  // 也支持通过 css 字符串模板获得和 普通 css 一致的书写体验
  card: css`
    color: ${token.colorTextTertiary};
    box-shadow: ${token.boxShadow};
    &:hover {
      color: ${token.colorTextSecondary};
      box-shadow: ${token.boxShadowSecondary};
    }

    padding: ${token.padding}px;
    border-radius: ${token.borderRadius}px;
    background: ${token.colorBgContainer};
    transition: all 100ms ${token.motionEaseInBack};

    margin-bottom: 8px;
    cursor: pointer;
  `,
}));

export default () => {
  // styles 对象在 useStyles 方法中默认会被缓存,所以不用担心 re-render 问题
  const { styles, cx, theme } = useStyles();

  return (
    // 使用 cx 可以组织 className
    <div className={cx('a-simple-create-style-demo-classname', styles.container)}>
      <div className={styles.card}>createStyles Demo</div>
      {/* theme 对象包含了所有的 token 与主题等信息 */}
      <div>当前主题模式:{theme.appearance}</div>
    </div>
  );
};

以上,就是CSS in JS目前所有的思想。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容