以前如果使用CSS-in-JS来编译项目样式文件,会优先考虑styled-components。 其特点是使用模板字符串来编译样式组件,使用方便css3 子类选择器,易于上手。 反对的声音之一是styled-components使用运行时机制,减少了产品的体积,担心运行时开销会造成一些性能损失。
前段时间我在个人项目中使用了 VanillaExtract(访问查看该项目)。 与其他 CSS-in-JS 解决方案不同,它可以在编译时编译 CSS 样式文件,实现零运行时并支持 TypeScript。
下面css3 子类选择器,我们将为您介绍VanillaExtract的特点和应用。
什么是香草精?
VanillaExtract 是一个新的 CSS-in-JS 库,用于编译 CSS 样式文件。 它于 2021 年开源,并在年度全球 CSS 报告中获得 CSS-in-JS 满意度第一名。
框架友好
VanillaExtract 是一个通用库,不绑定任何 JavaScript 框架。 你可以在React、Vue、Angular等框架中使用它,但在此之前,你需要让自己的创建工具支持它。
VanillaExtract已经集成了最流行的后端创建工具,包括:webpack、esbuild、Vite等。
本文以在Vite中使用为例。 首先,您需要安装两个库:@vanilla-extract/css 和 @vanilla-extract/vite-plugin。 **@vanilla-extract/css 是我们样式开发中主要使用的库**。
npm install @vanilla-extract/css @vanilla-extract/vite-plugin
然后将vanillaExtractPlugin插件添加到vite配置中。 CSS的编译和处理主要是通过这个插件完成的。 编译样式文件时,文件名必须以**.css.ts**结尾。
// vite.config.ts
import { defineConfig } from 'vite';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
export default defineConfig({
plugins: [vanillaExtractPlugin()],
});
零运行时间和 TS 支持
使用TypeScript是VanillaExtract的核心,它可以让VanillaExtract在创建时准确定位样式的位置并生成所有样式,就像SaaS和Less一样,这也与其他CSSinJS解决方案(StyledComponents和Emotion)不同。
使用 TypeScript 编译 CSS 样式的另一个用途是它为样式带来类型安全。 如果编译出不正确的值,编译时就会报错给我们。 编者也会在编码过程中给我们一些风格提示。
image.png常用样式API
本文介绍了日常开发中常用的一些样式API是如何在vanillaextract中编译的。 **@vanilla-extract/css 库的 style() 方法是常用的基本 API**。
样式文件需要以 **.css.ts** 结尾。 CSS样式属性名称采用驼峰式大小写书写,与CSS样式的正常书写没有太大区别。
// styled.css.ts
import { style } from '@vanilla-extract/css';
export const todoList = style({
marginTop: '20px',
background: '#ccc',
});
export const todoInfo = style({
paddingTop: '10px',
});
然后在我们的App组件中,导出样式文件并将值设置为标签的className属性的形参。 使用方法还是和其他CSS-in-JS库不同。 例如,styled-component 样式采用组件的形式。 来编译和使用。
// app.tsx
import * as styled from './styled.css';
const App = () => {
return
- 学习 React 开发</li>
- 学习 Node.js 开发</
li>
</ul>
</>
}
vanillaextract 支持一些伪类选择器、子选择器、媒体查询、**@supports** 以及通过引用 style() 函数创建的其他类。
export const todoInfo = style({
paddingTop: '10px',
// 伪类选择器
':hover': {
color: 'red',
},
selectors: {
// 选择自身的最后一个元素
'&:last-child': {
paddingBottom: '10px',
},
// 选择器还可以包含对其他作用域类名称的引用,这会改变 todoList 这个类的背景颜色
[`${todoList} &`]: {
background: 'yellow',
},
},
// 媒体查询
'@media': {
'screen and (min-width: 568px)': {
color: 'blue',
},
},
'@supports': {
// 摘自官网示例
'(display: grid)': {
display: 'grid',
},
},
});
文档中指出:为了提高可维护性,每个样式块只能针对单个元素。 这意味着你不能直接调整它的子元素或兄弟元素,例如以下需求:
.todo-list > li { // 期望的写法
color: green !important;
}
export const todoList = style({
marginTop: '20px',
background: '#ccc',
'& > li': { // 错误的实现
color: green !important;
}
});
VanillaExtract 提供了 GlobalStyle() API 来全局定位当前元素的子节点。 这里不需要担心重复,VanillaExtract 具有本地范围的类名,并且像 CSSModule 一样,不存在类名冲突的风险。
import { style, globalStyle } from '@vanilla-extract/css';
export const todoList = style({ ... });
// 对局部作用类名的样式设置
globalStyle(`${todoList} > li`, {
color: 'green !important',
});
VanillaExtract 全局样式不支持嵌套,某些情况下可能需要多次调用 globalStyle() 函数来设置样式。
globalStyle('html, body', {
margin: 0
});
globalStyle('a', {
color: 'blue',
});
globalStyle('a:hover', {
color: 'red',
});
风格组合,就像父类和泛型类型的继承关系一样,体现了风格的通用部分。
import { style } from '@vanilla-extract/css';
const base = style({ padding: 12 });
export const primary = style([
base,
{ background: 'blue' }
]);
export const secondary = style([
base,
{ background: 'aqua' }
]);
CSS 变量有时称为 CSS 自定义属性。 当定义了CSS变量后,它就可以在整个网站的样式文件中重复使用。
例如,在网站开发中,可能会有很多重复的值,比如颜色,用原生CSS编译如下:
// css 变量声明与使用
:root {
--blue-color: blue;
}
.one {
color: var(--blue-color);
}
.two {
color: var(--blue-color);
}
// javascript 操作 css
element.style.getPropertyValue("--blue-color");// 获取一个 Dom 节点上的 CSS 变量
getComputedStyle(element).getPropertyValue("--blue-color");// 获取任意 Dom 节点上的 CSS 变量
element.style.setProperty("--blue-color", jsVar + 4);// 修改一个 Dom 节点上的 CSS 变量
vanillaextract 没有重新发明轮子,而是广泛使用浏览器内置的原生 CSS 变量函数。 由于 vanillaextract 具有本地作用域的类名,因此另一个用途是能够在样式块内限定 CSS 变量的作用域。
创建主题
当应用程序只有一个全局主题时,建议使用createGlobalTheme方法。 使用起来并不复杂。 只需查看文档即可。
有时我们的应用程序会有多个主题,例如“深色模式”。 您需要做的第一件事是使用 createGlobalThemeContract() 方法创建主题合约。 这个key的值可以先设置为null,这样也保证了创建。 主体可以拥有正确的密钥。 以后“主题合约”返回的值将作为createTheme()方法的第一个参数传入。
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 };
应用主题
里面的createTheme()方法返回的值是一个类名,可以在HTML标签的className中使用它来声明CSS变量。 在应用程序需要获取CSS样式的根标签中,设置该类名。
import { darkTheme, lightTheme } from './styles/theme.css';
import { useState } from 'react';
const App = () => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
return (
<div id="app" className={isDarkTheme ? darkTheme : lightTheme}>
<button
type="button"
onClick={() => setIsDarkTheme((currentValue) => !currentValue)}
>
Switch to {isDarkTheme ? 'light' : 'dark'} theme
</button>
</div>
);
};
**代码片段中通过一个简单的Demo来展示如何应用主题。 如果想让体验更好,还可以通过监听系统主题来设置默认主题颜色。 请参阅项目 **#L27。
export const useSystemTheme = () => {
const [name, setName] = useState(Theme.Light);
useEffect(() => {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
setName(Theme.Dark);
} else {
setName(Theme.Light);
}
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (event: MediaQueryListEvent) => {
if (event.matches) {
setName(Theme.Dark);
} else {
setName(Theme.Light);
}
});
}, []);
return {
name,
isDarkMode: name === Theme.Dark,
isLightMode: name === Theme.Light,
};
};
当切换主题按钮时,可以看到CSS样式表中包含了以下两个主题的CSS变量。
使用主题
在组件样式中使用声明的主题变量。 vars 是 theme.css.ts 文件中导入的变量。
import { style } from '@vanilla-extract/css';
import { vars } from '../../styles/theme.css';
export const todoList = style({
marginTop: '20px',
backgroundColor: vars.colors.backgroundColor,
color: vars.colors.color,
});
我们来看看效果。 就像直接编写原生 CSS 变量一样,使用 var 来获取 CSS 变量,而不是直接进行值替换。
总结
在这篇文章中,我们主要介绍了 VanillaExtract 的一些特性及其在 React 中的应用。 与任何其他CSS-in-JS解决方案不同,它的样式不是在JavaScript运行时生成的,而是在编译阶段就已经完成了。
VanillaExtract 是一个新的 CSS-in-JS。 虽然目前它的使用量远低于竞争对手,但在 2021 年全球 CSS 满意度调查中仍然排名第一,也是一项值得关注的技术。