指导
在本节中,我们将安装 EffCSS 并了解如何使用它。
安装
在终端中输入:
# npm
npm i effcss
# pnpm
pnpm add effcss
# yarn
yarn add effcssCSS 类型
默认情况下,EffCSS 采用宽松模式:样式接受任意对象。若要启用严格的 CSS 类型检查(包括自动补全和无效值验证),请通过全局命名空间扩展(global namespace augmentation)在项目中定义一次 EffCSS.Properties。
例如,您可以安装 csstype
npm i -D csstype然后与 EffCSS 结合使用
// env.d.ts
import type { Properties as CSSProperties } from 'csstype';
declare global {
namespace EffCSS {
interface Properties extends CSSProperties {}
}
}
export {};现在,每个样式规则都能针对 CSS 属性获得完整的 TS 自动补全支持。
使用
使用 EffCSS 创建样式时,只需调用相应的工具函数即可。这些工具函数数量很少,且从名称上就能看出其用途。
所有的样式工具函数默认都是“惰性”的:只有在首次使用解析器(即调用 resolver() 或执行 resolver + '')时,样式才会被真正生成。如果样式中引用了其他标记(token),请将其包裹在函数 () => ({...}) 中,以确保其保持“惰性”。
下面我们来详细了解一下各个工具函数的具体用法。
id / className
id 会根据指定内容创建一条 CSS 规则,并返回该 id 解析函数:
import { id } from 'effcss';
const badge = id({ backgroundColor: 'red' });
badge(); // 'f_5-0'
'' + badge; // 'f_5-0'className 会根据指定内容创建一条 CSS 规则,并返回类名解析函数:
import { className } from 'effcss';
const btn = className({ color: 'white' });
btn(); // 'f_6-0'
'' + btn; // 'f_6-0'修饰符
通过向选择器解析器(基于 id 或 className)传入一个或多个修饰符,可以创建同一基础规则的变体。每个变体都会生成对应的标记(token),并按修饰符组合进行缓存。伪类和属性会被附加到选择器上;而 @media 或 @container 等“指令规则”(at-rules)则会以不同的嵌套层级包裹该规则:
const box = className(() => ({ color: 'red' }));
box(); // 'f_6-0' -> `.f_6-0 { color: red; }`
box(':hover'); // 'f_6-1' -> `.f_6-1:hover { color: red; }`
box('[data-state="open"]'); // 'f_6-2' -> `.f_6-2[data-state="open"] { color: red; }`
box('@media (max-width: 700px)'); // 'f_6-3' -> `@media (max-width: 700px) { .f_6-3 { ... } }`
box('@media (max-width: 700px)', ':hover'); // 'f_6-4' -> `@media (...) { .f_6-4:hover { ... } }`同一基础规则可在不同变体中复用——条件是在使用该规则的地方应用的,而非预先嵌入到规则定义之中。
延迟求值样式与 Token
如果你的样式引用了其他 Token(解析器),请将其包裹在函数 () => ({...}) 中。若在普通对象中直接传入已求值的 Token,则会抛出 TypeError;这是因为这样做破坏了“延迟求值”机制,并会引发模块副作用:
const size = variable('12px');
// ❌ 抛出:规则包含解析器结果
// 因此会产生模块副作用
className({ padding: size() });
// ✅ 惰性(lazy):仅在规则被使用时才对标记进行解析
className(() => ({ padding: size() }));函数形式实现了样式的“惰性”处理——在解析器(resolver)首次被调用或被强制转换为字符串之前,不会生成任何内容。这一机制适用于所有接受包含标记(token)对象的工具属性,包括 className、id、style 和 animation。
Native CSS nesting
任何样式对象都支持嵌套选择器,因此你可以添加伪类、属性、子元素以及组合选择器。& 代表当前选择器:
const card = className({
padding: '1rem',
'&:hover': { boxShadow: '0 0 8px' }, // `.f_6-0:hover`
'&[data-active]': { borderColor: 'blue' }, // `.f_6-0[data-active]`
'& > .title': { fontWeight: 600 } // `.f_6-0 > .title`
});在函数形式中,你甚至可以将完整的选择器(标记)作为嵌套键传入——它们会在规则被使用时才进行惰性解析:
const card = className(() => ({
padding: '1rem',
['& > ' + btn]: { marginTop: '1rem' } // `& > .f_6-0`
}));variable
variable 会创建一个 CSS @property 规则,并返回一个变量解析函数:
import { variable } from 'effcss';
const accent = variable('#ff0');
'' + accent; // '--f_2-0'
accent(); // 'var(--f_2-0)'
accent('green'); // 'var(--f_2-0, green)'你可以传入一个配置对象,以设置 syntax、inherits 和 initialValue:
const size = variable({ initialValue: '12px', syntax: '<length>', inherits: false });您可以使用相应的方法来获取和设置变量的初始值:
const size = variable('12px');
size(); // 将变量具体化
size.get(); // '12px'
size.set('24px'); // 更新 @property 规则
size.set((prev) => prev.replace('px', 'rem')); // 通过函数进行更新INFO
set/get 方法仅在变量被具体化(通过 resolver() 或 resolver + '')之后才有效。在此之前,get() 会返回空字符串,而 set() 则不产生任何效果。
animation
animation 会创建一个单一的 CSS @keyframes 规则,并返回一个关键帧名称解析器:
import { animation } from 'effcss';
const spin = animation({ from: { transform: 'rotate(0)' }, to: { transform: 'rotate(360deg)' } });
spin(); // 'f_3-0'
'' + spin; // 'f_3-0'
// 当关键帧引用 EffCSS 令牌(tokens)时的函数形式
const grow = animation(() => ({ from: { height: size() }, to: { height: '100px' } }));
grow(); // 'f_3-1'container
container 会创建一个 CSS @container 规则。将其转换为字符串时,会得到该 @container 规则;若对其进行调用,则会得到 name / type:
import { container } from 'effcss';
const card = container('inline-size');
'' + card; // '@container f_4-0'
card(); // 'f_4-0 / inline-size'
// 若未指定类型,则使用默认值
const plain = container();
'' + plain; // '@container f_4-1'
plain(); // 'f_4-1 / normal'font
font 会创建一个 CSS @font-face 规则,并返回一个可调用的字符串,其中包含基本的字体族名称以及可选的备用字体:
import { font } from 'effcss';
const Inter = font({ src: "url('/inter.woff2')" });
Inter(); // 'f_1-0'
Inter('sans-serif'); // 'f_1-0, sans-serif'
Inter('Arial', 'sans-serif'); // 'f_1-0, Arial, sans-serif'layers
layers 会利用传入的所有层,创建一个单一的 CSS @layer 声明规则。它会返回一个包含多个层解析函数的对象,每个层名称对应一个解析函数:
import { layers } from 'effcss';
const myLayers = layers(['reset', 'base']);
myLayers.reset(); // '@layer f_0-0'
'' + myLayers.reset; // '@layer f_0-0'
myLayers.base(); // '@layer f_0-1'style
style 用于创建任意样式,并返回一个空字符串解析函数;该函数会在首次被调用时将这些样式注入进去:
import { style } from 'effcss';
const custom = style({ body: { margin: 0 } });
custom(); // '' (样式已注入)
'' + custom; // ''
// 在样式中使用标记时请使用函数
const themed = style(() => ({ body: { color: accent(), ['& > ' + btn]: { marginTop: '1rem' } } }));
themed(); // ''style 支持原生 CSS 嵌套及特殊值:
// 数组中的值被展平为声明
style(() => ({ body: { margin: ['0', 'auto'] } }));
// 对象数组会产生多条具有相同键的规则
// (适用于任何层级)
style({
'@font-face': [
{ fontFamily: '"Bitstream Vera Serif Bold"', src: 'url("fira.woff2")' },
{ fontFamily: '"MyHelvetica"', src: 'local("Helvetica Neue Bold")', fontWeight: 'bold' }
]
});
// 空字符串值会生成一个不带内容的 @ 规则声明
style(() => ({ '@layer custom': '' })); // @layer custom;
// 驼峰命名(camelCase)的属性被转换为短横线命名(kebab-case)
style(() => ({ body: { fontFamily: 'serif' } }));theme
theme 将变量及其值归组为不同的主题变体,从而简化动态样式切换。它会返回一个控制器对象,该对象包含 vars、list、get 和 set 方法:
import { theme } from 'effcss';
const myTheme = theme({
vars: { bg: '#eff', fg: 'black' },
options: {
light: { bg: '#eff', fg: 'black' },
dark: { bg: '#111', fg: 'white' }
},
initial: 'light'
});
myTheme.get(); // 'light'
myTheme.set('dark'); // true
myTheme.get(); // 'dark'
myTheme.vars.bg(); // 'var(--f_2-0)'
myTheme.vars.bg.get(); // '#111'
myTheme.vars.bg.set('#eee'); // 覆盖单个变量serialize
serialize 会将所有已创建的样式表转换为包含 <style> 标签的 HTML 字符串:
import { className, serialize } from 'effcss';
className({ color: 'white' })();
// 将生成的 `<style>` 标签渲染到你的 HTML 中
const html = serialize();
// '<style data-effcss-key="f-6" data-effcss-count="1">.f_6-0 { color: white; }</style>'这样,样式可以在服务器端准备好,并在客户端复用。这对于 SSG/SSR 尤为有用。
setConfig / getConfig
getConfig 返回全局样式上下文的当前配置。setConfig 可用于修改配置;若在创建首个样式表之前调用,则会影响样式的生成:
import { setConfig, getConfig } from 'effcss';
setConfig({ key: 'app' }); // 必须在首次使用前调用
getConfig(); // { key: 'app' }若配置应用成功,setConfig 返回 true;若提供程序已被使用,则返回 false。键(key)必须以字母开头,且仅能包含字母、数字、_ 或 -。
clear
clear 会移除所有生成的样式并重置计数器:
import { clear } from 'effcss';
clear();context
context 允许你创建一组独立的样式,并返回一个包含所有 EffCSS 工具函数的对象(不含 context 本身)。每个 context 都拥有唯一的标识键(key)、计数器和样式表,因此不同 context 之间的标记(token)绝不会发生冲突:
import { context } from 'effcss';
const ctx = context({ key: 'app' });
const btn = ctx.className({ color: 'white' });
btn(); // 'app_6-0'
ctx.serialize(); // '<style data-effcss-key="app-6" data-effcss-count="1">...</style>'
ctx.getConfig(); // { key: 'app' }
ctx.clear(); // 清除样式,同时保留可用的上下文
ctx.dispose(); // 永久性地破坏了上下文getContexts 返回所有活跃的本地上下文,并以其键(key)作为索引。已销毁或已被垃圾回收的上下文不包含在内(此处采用弱引用):
import { getContexts } from 'effcss';
const ctx = context({ key: 'app' });
getContexts(); // { app: ctx }由于 context 样式可以独立进行序列化与还原(hydration),因此你可以利用这一特性在微前端之间实现样式隔离。此外,context 还实现了 Symbol.dispose,从而支持与 using 语句配合使用。