透过vanilla-extract 了解 Css in Js

写在前面

前不久,antd5更新了,其中最大的一个更新点就是css in js, 并称使用css in js后的antd “太香了”,今天这篇文章,我将用一个热门的css in js库,来带大家了解css in js这个古老而新颖的概念;
之所以说这是个古老而新颖的概念,是因为早在2014年就被提出;但又一直在不断的更新和推出新的解决方案,包括刚才提到的antd5也是其推出了更新的css in js解决方案应用在了组件库上。

初识Css in Js

我们先来看一个demo;我们搭建一个最简单的webpack项目,如下

|-- src
  |-- index.js
  |-- index.css
|-- webpack.comfig.js

webpack.config.js

const path = require('path')
module.exports = {
    mode: 'development',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'bundle')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
              }
              
        ]
    },
}

src/index.js

import './index.css'
var div = document.createElement('div')
div.classList.add('banner')
var root = document.getElementById('root')
root.append(div)

src/index.css

.banner {
  width: 150px;
  height: 150px;
  background: #f00;
}

代码意思,我们不做解释,看打包结果


虽然我们写了css文件,但打包出来的产物并没有css,并且,访问index.html,样式是生效的

这说明,css 被打包进了js中,这其实就是css in js的概念;或者我们应该认识到一件事就是css是可以打包进js中的

正式进入css in js

为什么会有css in js这个概念,首先我们先来看一下,传统css 有什么缺点

  • 文档级别,内容臃肿,可读性差
  • 选择器冲突问题
  • 动态变量支持不够友好
  • ...

其中最主要的就是动态变量了,虽然我们有各种css 预处理如less、scss这些, 但其呈现方式也有很大弊端

  • 构建时的方式,不够灵活
  • 运行时的方式,太过臃肿

那么,css in js能为我们带来什么呢?

  • 组件化思考模式,不再需要维护一堆样式表
  • CSS-in-JS 利用 JavaScript 环境的全部功能来增强CSS。(最强大,如变量、作用域等等)
  • JS CSS 代码共享
  • 动态的变量!

实践 - vanilla-extract

接下来,我将用一个比较热的css in js库,来带大家认识它,在此之前,大家要明白一个道理,css in js是一个概念,不是一个具体实现,所以,我们会发现,市面上有各种各样的实现方式,也呈现出了许多的库;
先来介绍一个 vanilla-extract;

  • 零运行时,支持TS(这是其最大的特点,这也将以为着,其产物会得到很大的优化)
  • 开源产品

这里我就不从0搭建一个react环境了,我们直接使用create-react-app;在搭建好react环境后,我们需要安装依赖

yarn add @vanilla-extract/css @vanilla-extract/webpack-plugin -D

这里我们将cra通过reject反编译成webpack配置,方便对其进行插件扩展;
然后增加webpack配置

const {
  VanillaExtractPlugin
} = require('@vanilla-extract/webpack-plugin');

module.exports = {
  plugins: [new VanillaExtractPlugin()]
};
浅试一下

vanilla-extract

  • 所有的类名是驼峰模式(camelCase),就像我们在jsx中写style那样;
  • 样式文件需要以*.css.ts结尾,是js/ts文件

知道以上两个规则,我们就可以体验一下了

写一个简单样式并应用

在vanilla-extract 中,我们可以想写ts那样写css,我们新建一个'style.css.ts'文件。利用其API,先写一个简单的demo;
style.css.ts

import { style } from '@vanilla-extract/css';
export const demo = style({
  width: 150,
  height: 150,
  backgroundColor: '#f00',
  color: '#fff'
});

app.ts

import { demo } from './style.css'
function App() {
  return (
    
css in js
); } export default App;

其中,在style.css.ts中,我们像写ts那样写css,并以JS变量的形式对其进行使用;


可以看到,除了达到了我们的预期外,还在类名上加入hash,就想我们再react中使用css modules那样,我们再也不用担心类名的冲突了;并且还支持ts类型校验,让我们写出更严谨的css

除了上面演示的,vanilla-extract 还支持各种选择器,如,伪类,子选择器等,

export const demo = style({
  width: 150,
  height: 150,
  backgroundColor: '#f00',
  color: '#fff',
  ':hover': {
    backgroundColor: '#009'
  }
});

并且,在vanilla-extract中,每个样式块只能针对单个元素。意思也就是说你不能直接对其子元素或兄弟元素做调整
比如,我们有这样的需求

.todo-list > li { // 期望的写法
    color: green !important;
}

这样写将是错误的

export const todoList = style({
  marginTop: '20px',
  background: '#ccc',
  '& > li': { // 错误的实现
    color: green
  }
});

也就是说,'只能选择自己',用官网的实例为

import { style } from '@vanilla-extract/css';
// Invalid example:
export const child = style({});
export const parent = style({
  selectors: {
    // ❌ ERROR: Targetting `child` from `parent`
    [`& ${child}`]: {...}
  }
});

// Valid example:
export const parent = style({});
export const child = style({
  selectors: {
    [`${parent} &`]: {...}
  }
});

如果想选择其他的元素,请使用globalStyle,如

import { globalStyle } from '@vanilla-extract/css';
globalStyle('html, body', {
  margin: 0
});
export const parentClass = style({});

globalStyle(`${parentClass} > a`, {
  color: 'pink'
});
写一个动态换肤功能

在没有vanilla-extract 或者 css in js之前,我们如果要用传统css方案实现一个动态换肤,需要写很多的运行时代码,或者用“换类名”的方式,更换类名,相当麻烦;这里我们利用css in js的JS能力,实现一个动态换肤,要实现换肤,最终要的便是变量的概念。我们可以这样来创建“主题变量”

import { createTheme, createThemeContract } from '@vanilla-extract/css';

const colors = createThemeContract({
  color: null,
  backgroundColor: null,
});

export const lightTheme = createTheme(colors, {
  color: '#000000',
  backgroundColor: '#ffffff',
});

export const darkTheme = createTheme(colors, {
  color: '#ffffff',
  backgroundColor: '#000000',
});
export const vars = { colors };

其中,createThemeContract为“主题契约”,主要是为了CSS 变量的类型化数据结构,与提供的主题实现的形状相匹配。通过传入一个现有的主题契约,而不是创建新的 CSS 变量,现有的变量被重用,并被分配给一个新的 CSS 类中的新值。之后将 “主题契约” 返回的值做为createTheme()方法的第一个参数传入。
vars是我么存变量的地方,方便我们使用,在晚上了上面的操作后,我们就可以对其进行应用,如

import { useState } from 'react';
import { darkTheme, lightTheme } from './style.css'
const App = () => {
  const [isDarkTheme, setIsDarkTheme] = useState(false);
  return (
    <>
      
css in js
); }; export default App;

createTheme方法返回一个类名,可以直接作用在标签上,这时,我们看控制台


这并不是css 属性,而是css变量,这里引用一下css 变量的使用以及定义

// css 变量声明与使用
:root {
--blue-color: blue;
}
.one {
color: var(--blue-color);
}
.two {
color: var(--blue-color);
}

同样的,我们通过上面的操作后,相当于在一个作用域下定义了一些css变量,接下来,就是在该作用域下使用这些变量

style.css.ts

...
export const vars = { colors };
export const essay = style({
  backgroundColor: vars.colors.backgroundColor,
  color: vars.colors.color,
})

值得一提的是,在书写过程中的ts提示,可见其强大



在使用完主题变量后,就可以应用这个类了。值得注意的,我们要注意作用域,要在上面定义的主题变量的作用域内使用,像下面这样

// ✅ 类名应用在变量作用域下

css in js

// ❌ 类名应用在作用域外

css in js

这时,我们再看,就实现了变量的应用和动态样式的切换(换肤功能)



思考

通过上面的demo,我们不免要发起思考

css in js方案,和行内样式、less这一类预处理方案有什么区别呢?
  • 写的是js/ts,你可以利用js的一切能力来加强css
  • 产物中不再包含css文件
  • 优秀的按需加载(webpack + js能力),从此告别babel-plugin-import
  • hash类名,不用再担心类名冲突
  • 零运行时(vanilla-extract ),产物更加简洁,变量模式更加优雅
什么场景下适合 css in js方案呢?
  • css in js是一个成熟的概念,且不断有新的实现突破,所以,成熟的css in js实现,是可以代替传统的css 方案的
  • 有多场景样式切换、多皮肤需求时,css in js在JS的加持下将更加优雅便利
  • 开发一个组件库时,css in js方案可以让产物更加单纯,参考antd5
市面上有哪些流行的css in js库
  • emotion
  • jss
  • styled-components (应用最广,但是运行时的模式,导致产物过大)
  • aphrodite
  • ...

总结

本文由浅到深的介绍了css in js方案,并通过介绍vanilla-extract,来让大家了解了css in js这种样式处理方式的优点。

CSS 设计的初衷是为了全局化的控制样式,通过选择器去扩展丰富实际的页面渲染,而 CSS-in-JS 并不是排斥 CSS 样式,而是说“样式”在现代化的组件颗粒化的发展下,使用 CSS-in-JS 能在瞬息万变的复杂应用场景下更加灵活的解决更多问题。

大家可以尝试使用哦;

本文demo源码:

  • https://github.com/sorryljt/css-in-js-webpack
  • https://github.com/sorryljt/css-in-js-cra-ts

你可能感兴趣的:(透过vanilla-extract 了解 Css in Js)