这是 StyleX 用户界面的样式系统的快速参考指南备忘单
入门 StyleX
介绍
StyleX 是一个 CSS In JS 的用户界面的样式系统
Vite
Babel 插件
Prettier
Bun
入门模板
配置编译器
1 2 3 4 5 6 7 8 9
| import plg from '@stylexjs/rollup-plugin';
const config = () => ({ plugins: [ plg({ ...options }) ] })
export default config;
|
使用样式
1 2 3 4 5
| import * as React from 'react'; import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ ... }); const colorStyles = stylex.create({ ... });
|
在 React 中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function ReactDemo( { color,isActive,style } ) { return ( <div {...stylex.props( styles.main, // 有条件地应用样式 isActive && styles.active, // 根据属性选择样式变体 colorStyles[color], // 将样式作为 props 传递 style, )} /> ); }
|
定义样式
1 2 3 4 5 6 7 8 9
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ root: { width: '100%', maxWidth: 800, minHeight: 40, }, });
|
样式是使用对象语法和 create()
API 定义的
安装
StyleX 运行时包
1
| npm install --save @stylexjs/stylex
|
编译器(生产) Rollup
1
| npm install --save-dev @stylexjs/rollup-plugin
|
修改 Rollup 配置 rollup.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import stylexPlugin from '@stylexjs/rollup-plugin';
const config = { input: './index.js', output: { file: './.build/bundle.js', format: 'es', }, plugins: [stylexPlugin({ fileName: './.build/stylex.css', dev: false, classNamePrefix: 'x', unstable_moduleResolution: { type: 'commonJS', rootDir: __dirname, }, })], };
export default config;
|
编译器(开发)
1
| npm install --save-dev @stylexjs/babel-plugin
|
修改 Babel 配置 (babel.config.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import styleX from '@stylexjs/babel-plugin';
const config = { plugins: [ [styleX, { dev: true, test: false, unstable_moduleResolution: { type: 'commonJS', rootDir: __dirname, } }], ], };
export default config;
|
编译器(生产) Webpack
1
| npm install --save-dev @stylexjs/webpack-plugin
|
修改 Webpack 配置 webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| const StylexPlugin = require('@stylexjs/webpack-plugin'); const path = require('path');
const config = (env, argv) => ({ entry: { main: './src/index.js', }, output: { path: path.resolve(__dirname, '.build'), filename: '[name].js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader', }, ], }, plugins: [ new StylexPlugin({ filename: 'styles.[contenthash].css', dev: argv.mode === 'development', runtimeInjection: false, classNamePrefix: 'x', unstable_moduleResolution: { type: 'commonJS', rootDir: __dirname, }, }), ], cache: true, });
module.exports = config;
|
编译器(生产) Next.js
1 2
| npm install --save-dev @stylexjs/nextjs-plugin \ @stylexjs/babel-plugin rimraf
|
在 package.json
添加配置
1 2 3 4 5 6 7
| { "scripts": { ..., "predev": "rimraf .next", "prebuild": "rimraf .next" } }
|
修改 Babel 配置 .babelrc.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| module.exports = { presets: ["next/babel"], plugins: [ [ "@stylexjs/babel-plugin", { dev: process.env.NODE_ENV === "development", test: process.env.NODE_ENV === "test", runtimeInjection: false, genConditionalClasses: true, treeshakeCompensation: true, unstable_moduleResolution: { type: "commonJS", rootDir: __dirname, }, }, ], ], };
|
修改 Next.js 配置 next.config.mjs
1 2 3 4 5 6 7 8 9
| import stylexPlugin from "@stylexjs/nextjs-plugin";
const nextConfig = {};
const __dirname = new URL(".", import.meta.url).pathname; export default stylexPlugin({ rootDir: __dirname, })(nextConfig);
|
仅限本地开发
要开始使用 StyleX
而无需配置编译器和构建过程,您可以安装本地开发运行时
1
| npm install --save-dev @stylexjs/dev-runtime
|
开发运行时必须导入到应用程序的 JavaScript
入口点并进行配置
1 2 3 4 5 6 7
| import inject from '@stylexjs/dev-runtime';
inject({ classNamePrefix: 'x', dev: true, test: false, });
|
用 ESLint 捕捉错误
1
| npm install --save-dev @stylexjs/eslint-plugin
|
StyleX 编译器不会验证您的样式,并且会编译许多无效样式。当您创作样式时,您应该使用 ESLint 插件来捕获这些错误。修改 ESLint 配置 .eslintrc.js
1 2 3 4 5 6
| module.exports = { plugins: ["@stylexjs"], rules: { "@stylexjs/valid-styles": "error", }, };
|
定义样式
创建样式
1 2 3 4 5 6 7 8 9 10 11 12
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5, color: 'rgb(60,60,60)', }, highlighted: { color: 'rebeccapurple', }, });
|
伪类
1 2 3 4 5 6 7
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ button: { backgroundColor: 'lightblue', }, });
|
样式名为 button
的背景样式上添加 :hover
和 :active
伪类
1 2 3 4 5 6 7 8 9 10 11
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ button: { backgroundColor: { default: 'lightblue', ':hover': 'blue', ':active': 'darkblue', }, }, });
|
伪元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ input: { '::placeholder': { color: '#999', }, color: { default: '#333', ':invalid': 'red', }, }, });
|
媒体查询(和其他@规则)
1 2 3 4 5 6 7 8 9 10 11
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ base: { width: { default: 800, '@media (max-width: 800px)': '100%', '@media (min-width: 1540px)': 1366, }, }, });
|
同样,媒体查询也可以作为样式值中的 “条件”
组合条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ button: { color: { default: 'var(--blue-link)', ':hover': { default: null, '@media (hover: hover)': 'scale(1.1)', }, ':active': 'scale(0.9)', }, }, });
|
当您需要组合媒体查询和伪选择器时,嵌套超过一层
后备样式
1 2 3 4 5
| .header { position: fixed; position: -webkit-sticky; position: sticky; }
|
使用 firstThatWorks
函数来实现相同的目的
1 2 3 4 5 6 7
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ header: { position: stylex.firstThatWorks('sticky', '-webkit-sticky', 'fixed'), }, });
|
关键帧动画
1 2 3 4 5 6 7 8 9 10 11 12 13
| import * as stylex from '@stylexjs/stylex';
const fadeIn = stylex.keyframes({ from: {opacity: 0}, to: {opacity: 1}, });
const styles = stylex.create({ base: { animationName: fadeIn, animationDuration: '1s', }, });
|
使用 stylex.keyframes()
函数来定义关键帧动画
动态样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ bar: (height) => ({ height, }), });
function MyComponent() { const [height, setHeight] = useState(10); return <div {...stylex.props(styles.bar(height))} />; }
|
注意:动态样式是一项高级功能,应谨慎使用。对于大多数用例,条件样式应该足够了。
使用样式
合并样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5, color: 'grey', }, highlighted: { color: 'rebeccapurple', }, });
<div {...stylex.props( styles.base, styles.highlighted )} />;
|
如果样式的顺序颠倒,文本将为灰色
1 2 3 4
| <div {...stylex.props( styles.highlighted, styles.base )} />
|
条件样式
1 2 3 4 5 6 7
| <div {...stylex.props( styles.base, props.isHighlighted && styles.highlighted, isActive ? styles.active : styles.inactive, )} />
|
通过使用常见的 JavaScript 模式(例如三元表达式和 &&
运算符),可以在运行时有条件地应用样式。 stylex.props
忽略虚假值,例如 null
、undefined
或 false
样式变体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ violet: { backgroundColor: { default: 'blueviolet', ':hover': 'darkviolet', }, color: 'white', }, gray: { backgroundColor: { default: 'gainsboro', ':hover': 'lightgray', }, }, });
|
然后,通过使用 variant
属性作为样式对象上的键来应用适当的样式
1 2 3 4 5 6 7
| function Button({variant, ...props}) { return ( <button {...props} {...stylex.props(styles[variant])} /> ); }
|
样式作为道具
1
| <CustomComponent style={styles.base} />
|
stylex.props
函数返回具有 className
和 style
的对象。当样式要合并到组件内时不要使用它:
1 2 3 4
| <CustomComponent style={stylex.props(styles.base)} />
|
1 2 3 4 5 6
| <CustomComponent style={[ styles.base, isHighlighted && styles.highlighted ]} />
|
接受组件中的样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ base: { }, });
function CustomComponent({style}) { return ( <div {...stylex.props(styles.base, style)} /> ); }
|
将其与 stylex.props
函数一起应用
“取消设置”样式
1 2 3 4 5 6 7
| import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({ base: { color: null, }, });
|
将样式属性设置为 null
会删除 StyleX 之前为其应用的任何样式。
主题
使用媒体查询
1 2 3 4 5 6 7 8 9 10 11 12
| import * as stylex from '@stylexjs/stylex';
const DARK = '@media (prefers-color-scheme: dark)';
export const colors = stylex.defineVars({ primaryText: {default: 'black', [DARK]: 'white'}, secondaryText: {default: '#333', [DARK]: '#ccc'}, accent: {default: 'blue', [DARK]: 'lightblue'}, background: {default: 'white', [DARK]: 'black'}, lineColor: {default: 'gray', [DARK]: 'lightgray'}, });
|
定义变量
1 2 3 4 5 6 7 8 9
| import * as stylex from '@stylexjs/stylex';
export const tokens = stylex.defineVars({ primaryText: 'black', secondaryText: '#333', borderRadius: '4px', fontFamily: 'system-ui, sans-serif', fontSize: '16px', });
|
这定义了 HTML 文档的 :root
处的变量。它们可以作为常量导入并在 stylex.create
调用中使用。
使用变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import * as stylex from '@stylexjs/stylex';
const DARK = '@media (prefers-color-scheme: dark)';
export const colors = stylex.defineVars({ primaryText: {default: 'black', [DARK]: 'white'}, secondaryText: {default: '#333', [DARK]: '#ccc'}, accent: {default: 'blue', [DARK]: 'lightblue'}, background: {default: 'white', [DARK]: 'black'}, lineColor: {default: 'gray', [DARK]: 'lightgray'}, });
export const spacing = stylex.defineVars({ none: '0px', xsmall: '4px', small: '8px', medium: '12px', large: '20px', xlarge: '32px', xxlarge: '48px', xxxlarge: '96px', });
|
然后就可以像这样导入和使用这些样式:
1 2 3 4 5 6 7 8 9 10
| import * as stylex from '@stylexjs/stylex'; import {colors, spacing} from '../tokens.stylex';
const styles = stylex.create({ container: { color: colors.primaryText, backgroundColor: colors.background, padding: spacing.medium, }, });
|
定义变量时的规则
变量必须定义在 .stylex.js
文件中,变量必须位于具有以下扩展名之一的文件中:
- .stylex.js
- .stylex.mjs
- .stylex.cjs
- .stylex.ts
- .stylex.tsx
- .stylex.jsx
变量必须命名为 exports
1 2 3 4 5 6 7 8
| export const colors = stylex.defineVars({ });
const sizeVars = { ... };
export const sizes = stylex.defineVars(sizeVars);
|
不允许:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export default stylex.defineVars({ });
const x = stylex.defineVars({ }); export const colors = x;
export const colors = { foregrounds: stylex.defineVars({ }), backgrounds: stylex.defineVars({ }), };
|
创建主题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import * as stylex from '@stylexjs/stylex'; import {colors, spacing} from './tokens.stylex';
const DARK = '@media (prefers-color-scheme: dark)';
export const dracula = stylex.createTheme(colors, { primaryText: {default: 'purple', [DARK]: 'lightpurple'}, secondaryText: {default: 'pink', [DARK]: 'hotpink'}, accent: 'red', background: {default: '#555', [DARK]: 'black'}, lineColor: 'red', });
|
应用主题,主题对象类似于使用 stylex.create()
创建的样式对象。使用 stylex.props()
将它们应用于元素,以覆盖该元素及其所有后代的变量。
1 2 3
| <div {...stylex.props(dracula, styles.container)}> {children} </div>;
|
- 创建主题时必须覆盖变量组中的所有变量。这一选择是为了帮助发现意外遗漏
- 主题可以在代码库中的任何位置使用
stylex.createTheme()
创建,并在文件或组件之间传递
- 如果同一变量组的多个主题应用于同一 HTML 元素,则最后应用的主题获胜
变量类型
1 2 3 4 5 6 7 8 9
| import * as stylex from '@stylexjs/stylex';
export const tokens = stylex.defineVars({ primaryTxt: stylex.types.color('black'), secondaryTxt: stylex.types.color('#333'), borderRadius: stylex.types.length('4px'), angle: stylex.types.angle('0deg'), int: stylex.types.integer(2), });
|
所有值都可以是任意字符串。要将类型分配给变量,可以使用适当的类型函数包装它们
变量类型 & 条件值
1 2 3 4 5 6 7 8
| import * as stylex from '@stylexjs/stylex';
export const colors = stylex.defineVars({ primaryText: stylex.types.color({ default: 'black', [DARK]: 'white' }), });
|
用法保持不变,以上内容完全有效
源代码中的类型安全
1 2 3 4 5 6 7 8 9 10 11
| import * as stylex from '@stylexjs/stylex'; import {tokens} from './tokens.stylex.js';
export const high = stylex.defineVars({ primaryTxt: stylex.types.color('black'), secondaryTxt: stylex.types.color('#222'), borderRadius: stylex.types.length('8px'), angle: stylex.types.angle('0deg'), int: stylex.types.integer(4), });
|
当在 stylex.defineVars
中使用特定类型声明变量时,静态类型将强制在 stylex.createTheme
调用中为该变量设置主题时使用相同类型的函数
动画渐变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import * as stylex from '@stylexjs/stylex'; import {tokens} from './tokens.stylex';
const rotate = stylex.keyframes({ from: { [tokens.angle]: '0deg' }, to: { [tokens.angle]: '360deg' }, });
const styles = stylex.create({ gradient: { backgroundImage: `conic-gradient(from ${tokens.angle}, ...colors)`, animationName: rotate, animationDuration: '10s', animationTimingFunction: 'linear', animationIterationCount: 'infinite', }, })
|
可以通过对其中使用的角度进行动画处理来对渐变进行动画处理
模拟 round()
现代浏览器开始支持 CSS 中的 round() 函数。
1 2 3 4 5 6 7 8 9
| const styles = stylex.create({ gradient: { [tokens.int]: `calc(16 / 9)`
[tokens.int]: `calc((16 / 9) + 0.5)` }, })
|
该功能通过一个整数类型的变量来模拟
TypeScript 类型
StyleXStyles 样式 props 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import type {StyleXStyles} from '@stylexjs/stylex'; import * as stylex from '@stylexjs/stylex';
type Props = { ... style?: StyleXStyles, };
function MyComponent( { style, ...otherProps }: Props ) { return ( <div {...stylex.props( localStyles.foo, localStyles.bar, style )} > {/* ... */} </div> ); }
|
StyleXStylesWithout 禁止属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import type {StyleXStylesWithout} from '@stylexjs/stylex'; import * as stylex from '@stylexjs/stylex';
type NoLayout = StyleXStylesWithout<{ position: unknown, display: unknown, top: unknown, start: unknown, end: unknown, bottom: unknown, border: unknown, borderWidth: unknown, borderBottomWidth: unknown, borderEndWidth: unknown, borderStartWidth: unknown, borderTopWidth: unknown, margin: unknown, marginBottom: unknown, marginEnd: unknown, marginStart: unknown, marginTop: unknown, padding: unknown, paddingBottom: unknown, paddingEnd: unknown, paddingStart: unknown, paddingTop: unknown, width: unknown, height: unknown, flexBasis: unknown, overflow: unknown, overflowX: unknown, overflowY: unknown, }>;
type Props = { style?: NoLayout, };
function MyComponent({style, ...}: Props) { return ( <div {...stylex.props(localStyles.foo, localStyles.bar, style)} > {/* ... */} </div> ); }
|
此处对象类型中列出的属性将被禁止,但所有其他样式仍将被接受。
从一组样式属性中接受
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import type {StyleXStyles} from '@stylexjs/stylex';
type Props = { style?: StyleXStyles<{ color?: string; backgroundColor?: string; borderColor?: string; borderTopColor?: string; borderEndColor?: string; borderBottomColor?: string; borderStartColor?: string; }>; };
|
限制样式的可能值
1 2 3 4 5 6 7 8 9 10
| import type {StyleXStyles} from '@stylexjs/stylex';
type Props = { ... style?: StyleXStyles<{ marginTop: 0 | 4 | 8 | 16 }>, };
|
VarGroup
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red', backgroundColor: 'blue', });
export type Vars = typeof vars;
|
VarGroup 是调用 stylex.defineVars
生成的对象的类型。它将键映射到 CSS 自定义属性的引用
StaticStyles
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import type {StaticStyles} from '@stylexjs/stylex';
type Props = { style?: StaticStyles<{ color?: 'red' | 'blue' | 'green'; padding?: 0 | 4 | 8 | 16 | 32; backgroundColor?: string; borderColor?: string; borderTopColor?: string; borderEndColor?: string; borderBottomColor?: string; borderStartColor?: string; }>; };
|
不允许使用函数定义的动态样式
Theme
1 2 3 4 5 6 7 8 9
| import type {VarGroup} from '@stylexjs/stylex'; import * as stylex from '@stylexjs/stylex';
import {vars} from './vars.stylex';
export const theme: Theme<typeof vars> = stylex.createTheme(vars, { color: 'red', backgroundColor: 'blue', });
|