美文网首页
透过vanilla-extract 了解 Css in Js

透过vanilla-extract 了解 Css in Js

作者: 喜剧之王爱创作 | 来源:发表于2023-03-05 21:46 被阅读0次

    写在前面

    前不久,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 (
        <div className={demo}>
            css in js
        </div>
      );
    }
    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 (
        <>
          <div className={isDarkTheme ? darkTheme : lightTheme}>
            css in js
          </div>
          <button
            type="button"
            onClick={() => setIsDarkTheme((currentValue) => !currentValue)}
           >
             切换 {isDarkTheme ? 'light' : 'dark'} 主题
           </button>
        </>
      );
    };
    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提示,可见其强大



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

    // ✅ 类名应用在变量作用域下
    <div className={isDarkTheme ? darkTheme : lightTheme}>
        <p className={essay}>css in js</p>
    </div>
    // ❌ 类名应用在作用域外
     <p className={essay}>css in js</p>
    

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



    思考

    通过上面的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源码:

    相关文章

      网友评论

          本文标题:透过vanilla-extract 了解 Css in Js

          本文链接:https://www.haomeiwen.com/subject/mkcpldtx.html