mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
♻️ refactor: refactor static style (#11010)
* refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style * refactor: refactor static style
This commit is contained in:
@@ -0,0 +1,959 @@
|
||||
# createStaticStyles 迁移指南
|
||||
|
||||
## 📖 概述
|
||||
|
||||
`createStaticStyles` 是 `antd-style` 提供的静态样式创建函数,相比 `createStyles`(hook 方案)具有零运行时开销的优势。样式在模块加载时计算一次,而不是每次组件渲染时计算。
|
||||
|
||||
## 🎯 适用场景
|
||||
|
||||
### ✅ 可以优化的场景
|
||||
|
||||
1. **纯静态样式**:不依赖运行时动态值
|
||||
2. **使用标准 token**:所有 token 都在 `cssVar.json` 中有对应项
|
||||
3. **简单的条件逻辑**:可以通过静态样式拆分处理
|
||||
|
||||
### ❌ 无法优化的场景
|
||||
|
||||
1. **JS 计算函数**:`readableColor()`, `chroma()`, `mix()`, `calc()` 中使用 token 数值
|
||||
2. **复杂的动态 props**:需要运行时计算的复杂逻辑
|
||||
3. **动态 prefixCls**:需要运行时传入的类名前缀(但可以硬编码为 `'ant'`)
|
||||
|
||||
## 🔄 基本转换步骤
|
||||
|
||||
### 1. 样式文件转换
|
||||
|
||||
**之前(createStyles):**
|
||||
|
||||
```typescript
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => {
|
||||
return {
|
||||
root: css`
|
||||
color: ${token.colorText};
|
||||
font-size: ${token.fontSize}px;
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**之后(createStaticStyles):**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => {
|
||||
return {
|
||||
root: css`
|
||||
color: ${cssVar.colorText};
|
||||
font-size: ${cssVar.fontSize};
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 组件文件转换
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { useStyles } from './style';
|
||||
|
||||
const Component = () => {
|
||||
const { styles, cx } = useStyles();
|
||||
return <div className={cx(styles.root, className)} />;
|
||||
};
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
import { cx } from 'antd-style';
|
||||
import { styles } from './style';
|
||||
|
||||
const Component = () => {
|
||||
return <div className={cx(styles.root, className)} />;
|
||||
};
|
||||
```
|
||||
|
||||
## 🛠️ 常见场景处理
|
||||
|
||||
### 场景 1: Token 转换
|
||||
|
||||
**规则:**
|
||||
|
||||
- `token.xxx` → `cssVar.xxx`
|
||||
- 注意:`cssVar.fontSize` 已经包含 `px` 单位,不需要再加 `px`
|
||||
|
||||
**示例:**
|
||||
|
||||
```typescript
|
||||
// ❌ 错误
|
||||
font-size: ${cssVar.fontSize}px; // cssVar.fontSize 已经是 "14px"
|
||||
|
||||
// ✅ 正确
|
||||
font-size: ${cssVar.fontSize}; // 直接使用
|
||||
```
|
||||
|
||||
**特殊情况 - calc ():**
|
||||
|
||||
```typescript
|
||||
// ❌ 错误
|
||||
calc(${token.fontSize}px * 2.5)
|
||||
|
||||
// ✅ 正确
|
||||
calc(${cssVar.fontSize} * 2.5) // cssVar.fontSize 已经包含单位
|
||||
```
|
||||
|
||||
### 场景 2: 动态 Props → CSS 变量
|
||||
|
||||
**适用:** 数值、字符串类型的 props
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 在样式文件中使用 CSS 变量(带默认值)
|
||||
2. 在组件中通过 `style` prop 设置 CSS 变量
|
||||
|
||||
**示例:**
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
export const styles = createStaticStyles(({ css }) => {
|
||||
return {
|
||||
root: css`
|
||||
width: var(--component-size, 24px);
|
||||
height: var(--component-size, 24px);
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const Component = ({ size = 24, style, ...rest }) => {
|
||||
const cssVariables = useMemo<Record<string, string>>(
|
||||
() => ({
|
||||
'--component-size': `${size}px`,
|
||||
}),
|
||||
[size],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.root}
|
||||
style={{
|
||||
...cssVariables,
|
||||
...style,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Video`: `maxHeight`, `maxWidth`, `minHeight`, `minWidth`
|
||||
- `ScrollShadow`: `size`
|
||||
- `MaskShadow`: `size`
|
||||
- `ColorSwatches`: `size`
|
||||
- `Grid`: `rows`, `maxItemWidth`, `gap`
|
||||
- `Layout`: `headerHeight`
|
||||
- `Footer`: `contentMaxWidth`
|
||||
|
||||
### 场景 3: 布尔值 Props → 静态样式拆分
|
||||
|
||||
**适用:** 简单的布尔值 props(2-3 个)
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 创建所有可能的组合样式
|
||||
2. 运行时使用 `cx` 组合
|
||||
|
||||
**示例:**
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
export const styles = createStaticStyles(({ css }) => {
|
||||
return {
|
||||
root: css`
|
||||
/* base styles */
|
||||
`,
|
||||
root_closable_true: css`
|
||||
/* closable styles */
|
||||
`,
|
||||
root_closable_false: css`
|
||||
/* no closable styles */
|
||||
`,
|
||||
root_hasTitle_true: css`
|
||||
/* has title styles */
|
||||
`,
|
||||
root_hasTitle_false: css`
|
||||
/* no title styles */
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
const Component = ({ closable, hasTitle }) => {
|
||||
const className = cx(
|
||||
styles.root,
|
||||
styles[`root_closable_${!!closable}`],
|
||||
styles[`root_hasTitle_${!!hasTitle}`],
|
||||
);
|
||||
return <div className={className} />;
|
||||
};
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Alert`: `closable`, `hasTitle`, `showIcon` → 8 个组合(2×2×2)
|
||||
- `Image`: `alwaysShowActions` → 2 个样式
|
||||
- `StoryBook`: `noPadding` → 2 个样式
|
||||
|
||||
### 场景 4: isDarkMode → 静态样式拆分
|
||||
|
||||
**适用:** 依赖 `isDarkMode` 的条件样式
|
||||
|
||||
**有两种处理方式:**
|
||||
|
||||
#### 方式 A: 直接条件选择(简单场景)
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 创建 `Dark` 和 `Light` 两个静态样式
|
||||
2. 运行时根据 `theme.isDarkMode` 选择
|
||||
|
||||
**示例:**
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => {
|
||||
return {
|
||||
rootDark: css`
|
||||
background: ${cssVar.colorFillTertiary};
|
||||
color: ${cssVar.colorTextLightSolid};
|
||||
`,
|
||||
rootLight: css`
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
color: ${cssVar.colorText};
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { useThemeMode } from 'antd-style';
|
||||
|
||||
const Component = () => {
|
||||
const { isDarkMode } = useThemeMode();
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
isDarkMode ? styles.rootDark : styles.rootLight
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 方式 B: 使用 cva 将 isDarkMode 作为 variant(推荐,适用于复杂场景)
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 创建 `Dark` 和 `Light` 两个静态样式
|
||||
2. 在 `cva` 中将 `isDarkMode` 作为 variant prop
|
||||
3. 运行时直接传入 `isDarkMode` 值
|
||||
|
||||
**示例:**
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => {
|
||||
return {
|
||||
filledDark: css`
|
||||
background: ${cssVar.colorFillTertiary};
|
||||
color: ${cssVar.colorTextLightSolid};
|
||||
`,
|
||||
filledLight: css`
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
color: ${cssVar.colorText};
|
||||
`,
|
||||
outlined: css`
|
||||
border: 1px solid ${cssVar.colorBorder};
|
||||
`,
|
||||
root: css`
|
||||
/* base styles */
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export const variants = cva(styles.root, {
|
||||
defaultVariants: {
|
||||
isDarkMode: false,
|
||||
variant: 'filled',
|
||||
},
|
||||
variants: {
|
||||
isDarkMode: {
|
||||
false: null,
|
||||
true: null, // isDarkMode 本身不添加样式,通过 compoundVariants 组合
|
||||
},
|
||||
variant: {
|
||||
filled: null, // variant 本身不添加样式,通过 compoundVariants 组合
|
||||
outlined: styles.outlined,
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
class: styles.filledDark,
|
||||
isDarkMode: true,
|
||||
variant: 'filled',
|
||||
},
|
||||
{
|
||||
class: styles.filledLight,
|
||||
isDarkMode: false,
|
||||
variant: 'filled',
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { useThemeMode } from 'antd-style';
|
||||
import { variants } from './style';
|
||||
|
||||
const Component = ({ variant = 'filled' }) => {
|
||||
const { isDarkMode } = useThemeMode();
|
||||
return (
|
||||
<div
|
||||
className={variants({ isDarkMode, variant })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**优势:**
|
||||
|
||||
- ✅ 不需要 `useMemo` 动态创建 variants
|
||||
- ✅ 更符合 `cva` 的设计理念
|
||||
- ✅ 代码更简洁,性能更好
|
||||
- ✅ 类型安全,IDE 自动补全
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `TypewriterEffect`: `textDark` / `textLight`(方式 A)
|
||||
- `Collapse`: `filledDark` / `filledLight`(可优化为方式 B)
|
||||
- `Hotkey`: `inverseThemeDark` / `inverseThemeLight`(可优化为方式 B)
|
||||
- `GuideCard`: `filledDark` / `filledLight`(可优化为方式 B)
|
||||
- `GradientButton`: `buttonDark` / `buttonLight`(方式 A)
|
||||
|
||||
### 场景 5: responsive → 静态 responsive
|
||||
|
||||
**适用:** 使用响应式断点
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 导入静态 `responsive` from `antd-style`
|
||||
2. 使用 `responsive.sm` 替代 `responsive.mobile`
|
||||
3. 从 `createStyles` 参数中移除 `responsive`
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, responsive }) => ({
|
||||
root: css`
|
||||
${responsive.mobile} {
|
||||
padding: 12px;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { responsive } from 'antd-style';
|
||||
|
||||
export const styles = createStaticStyles(({ css }) => ({
|
||||
root: css`
|
||||
${responsive.sm} {
|
||||
padding: 12px;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**注意:**
|
||||
|
||||
- `responsive.mobile` → `responsive.sm`
|
||||
- 静态 `responsive` 提供:`xs`, `sm`, `md`, `lg`, `xl`, `xxl`
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Header`: `responsive.mobile` → `responsive.sm`
|
||||
- `FormModal`: `responsive.mobile` → `responsive.sm`
|
||||
- `Hero`: `responsive.mobile` → `responsive.sm`
|
||||
|
||||
### 场景 6: stylish → lobeStaticStylish
|
||||
|
||||
**适用:** 使用自定义 `stylish` 工具
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 导入 `lobeStaticStylish` from `@/styles`
|
||||
2. 替换 `stylish.xxx` → `lobeStaticStylish.xxx`
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, stylish }) => ({
|
||||
root: css`
|
||||
${stylish.blur};
|
||||
${stylish.variantFilled};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
import { lobeStaticStylish } from '@/styles';
|
||||
|
||||
export const styles = createStaticStyles(({ css }) => ({
|
||||
root: css`
|
||||
${lobeStaticStylish.blur};
|
||||
${lobeStaticStylish.variantFilled};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Button`: `stylish.blur` → `lobeStaticStylish.blur`
|
||||
- `Hero`: `stylish.gradientAnimation` → `lobeStaticStylish.gradientAnimation`
|
||||
|
||||
### 场景 7: prefixCls → 硬编码
|
||||
|
||||
**适用:** 使用动态 `prefixCls` 参数
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 在文件顶部硬编码 `const prefixCls = 'ant'`
|
||||
2. 从 `createStyles` 参数中移除 `prefixCls`
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
export const useStyles = createStyles(({ css }, prefixCls: string) => ({
|
||||
root: css`
|
||||
.${prefixCls}-button {
|
||||
/* styles */
|
||||
}
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
const prefixCls = 'ant';
|
||||
|
||||
export const styles = createStaticStyles(({ css }) => ({
|
||||
root: css`
|
||||
.${prefixCls}-button {
|
||||
/* styles */
|
||||
}
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Alert`, `Collapse`, `FormModal`, `Image`, `Burger`, `DraggablePanel`, `DraggableSideNav`, `Toc`, `ColorSwatches`, `EmojiPicker`, `Form`, `awesome/Features`
|
||||
|
||||
### 场景 8: readableColor () → Token 替换
|
||||
|
||||
**适用:** 使用 `readableColor()` 计算对比色
|
||||
|
||||
**规则:**
|
||||
|
||||
- `readableColor(token.colorPrimary)` → `cssVar.colorTextLightSolid`(主色背景用白色文字)
|
||||
- `readableColor(token.colorTextQuaternary)` → `cssVar.colorText`(浅色背景用深色文字)
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { readableColor } from 'polished';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
checked: css`
|
||||
background-color: ${token.colorPrimary};
|
||||
color: ${readableColor(token.colorPrimary)};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
checked: css`
|
||||
background-color: ${cssVar.colorPrimary};
|
||||
color: ${cssVar.colorTextLightSolid};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Checkbox`: `readableColor(token.colorPrimary)` → `cssVar.colorTextLightSolid`
|
||||
|
||||
### 场景 9: rgba () → color-mix ()
|
||||
|
||||
**适用:** 使用 `rgba()` 设置透明度
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 使用 CSS 原生的 `color-mix()` 函数
|
||||
2. 格式:`color-mix(in srgb, ${cssVar.xxx} alpha%, transparent)`
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { rgba } from 'polished';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
root: css`
|
||||
background-color: ${rgba(token.colorBgLayout, 0.4)};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
background-color: color-mix(in srgb, ${cssVar.colorBgLayout} 40%, transparent);
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Header`: `rgba(cssVar.colorBgLayout, 0.4)` → `color-mix(...)`
|
||||
- `FormModal`: `rgba(cssVar.colorBgContainer, 0)` → `color-mix(...)`
|
||||
|
||||
### 场景 10: keyframes → css
|
||||
|
||||
**适用:** 使用 `keyframes` 创建动画
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. 在 `createStaticStyles` 外部定义 `keyframes`
|
||||
2. 在样式内部使用
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
export const useStyles = createStyles(({ css, keyframes }) => {
|
||||
const spin = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
`;
|
||||
return {
|
||||
icon: css`
|
||||
animation: ${spin} 1s linear infinite;
|
||||
`,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
import { keyframes } from 'antd-style';
|
||||
|
||||
const spin = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
export const styles = createStaticStyles(({ css }) => ({
|
||||
icon: css`
|
||||
animation: ${spin} 1s linear infinite;
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `Icon`: `keyframes` 动画
|
||||
- `Skeleton`: `keyframes` shimmer 动画
|
||||
|
||||
## ⚠️ 反模式:避免使用 createVariants (isDarkMode)
|
||||
|
||||
**不推荐的做法:**
|
||||
|
||||
```typescript
|
||||
// ❌ 不推荐:在组件中动态创建 variants
|
||||
export const createVariants = (isDarkMode: boolean) =>
|
||||
cva(styles.root, {
|
||||
variants: {
|
||||
variant: {
|
||||
filled: isDarkMode ? styles.filledDark : styles.filledLight,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 组件中
|
||||
const variants = useMemo(() => createVariants(isDarkMode), [isDarkMode]);
|
||||
```
|
||||
|
||||
**推荐的做法:**
|
||||
|
||||
将 `isDarkMode` 作为 `cva` 的 variant prop(见场景 4 方式 B),这样:
|
||||
|
||||
- ✅ 不需要 `useMemo` 动态创建
|
||||
- ✅ 更符合 `cva` 的设计理念
|
||||
- ✅ 代码更简洁,性能更好
|
||||
- ✅ 类型安全,IDE 自动补全
|
||||
|
||||
```typescript
|
||||
// ✅ 推荐:将 isDarkMode 作为 variant prop
|
||||
export const variants = cva(styles.root, {
|
||||
variants: {
|
||||
isDarkMode: {
|
||||
false: null,
|
||||
true: null,
|
||||
},
|
||||
variant: {
|
||||
filled: null,
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
class: styles.filledDark,
|
||||
isDarkMode: true,
|
||||
variant: 'filled',
|
||||
},
|
||||
{
|
||||
class: styles.filledLight,
|
||||
isDarkMode: false,
|
||||
variant: 'filled',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 组件中
|
||||
const { isDarkMode } = useThemeMode();
|
||||
const className = variants({ isDarkMode, variant: 'filled' });
|
||||
```
|
||||
|
||||
## ⚠️ 无法优化的场景
|
||||
|
||||
### 1. JS 计算函数
|
||||
|
||||
**无法优化:**
|
||||
|
||||
- `chroma()` - 颜色计算库
|
||||
- `readableColor()` - 需要运行时计算(但可以用 token 替代)
|
||||
- `mix()` - 颜色混合计算
|
||||
- `calc()` 中使用 token 数值进行复杂计算
|
||||
|
||||
**示例:**
|
||||
|
||||
```typescript
|
||||
// ❌ 无法优化
|
||||
const scale = chroma.bezier([token.colorText, backgroundColor]).scale().colors(6);
|
||||
```
|
||||
|
||||
### 2. 复杂的动态 Props
|
||||
|
||||
**无法优化:**
|
||||
|
||||
- 需要复杂计算的 props
|
||||
- 对象 / 数组类型的 props
|
||||
- 函数类型的 props
|
||||
|
||||
### 3. useTheme Hook
|
||||
|
||||
**无法优化:**
|
||||
|
||||
- 直接使用 `useTheme()` hook 获取运行时值
|
||||
- 例如:`awesome/Giscus/style.ts` 使用 `useTheme()` 获取主题值
|
||||
|
||||
## 📋 迁移检查清单
|
||||
|
||||
### 样式文件检查
|
||||
|
||||
- [ ] `createStyles` → `createStaticStyles`
|
||||
- [ ] `token.xxx` → `cssVar.xxx`
|
||||
- [ ] 移除 `px` 后缀(`cssVar` 已包含单位)
|
||||
- [ ] `responsive.mobile` → `responsive.sm`(如果使用)
|
||||
- [ ] `stylish.xxx` → `lobeStaticStylish.xxx`(如果使用)
|
||||
- [ ] `rgba()` → `color-mix()`(如果使用)
|
||||
- [ ] `readableColor()` → token 替换(如果使用)
|
||||
- [ ] `prefixCls` 参数 → 硬编码 `const prefixCls = 'ant'`(如果使用)
|
||||
- [ ] `isDarkMode` → 静态样式拆分(如果使用)
|
||||
- [ ] 动态 props → CSS 变量(如果使用)
|
||||
|
||||
### 组件文件检查
|
||||
|
||||
- [ ] `useStyles()` → `import { styles } from './style'`
|
||||
- [ ] `import { cx } from 'antd-style'`(如果需要)
|
||||
- [ ] `import { useTheme } from 'antd-style'`(如果需要 `theme.isDarkMode`)
|
||||
- [ ] 动态 props → CSS 变量设置(如果使用)
|
||||
- [ ] `isDarkMode` 条件 → `theme.isDarkMode` 判断(如果使用)
|
||||
|
||||
## 🎯 优化优先级
|
||||
|
||||
### 高优先级(简单优化)
|
||||
|
||||
1. ✅ 纯静态样式(无动态 props)
|
||||
2. ✅ `isDarkMode` 拆分
|
||||
3. ✅ `responsive.mobile` → `responsive.sm`
|
||||
4. ✅ `stylish` → `lobeStaticStylish`
|
||||
5. ✅ `readableColor()` → token 替换
|
||||
|
||||
### 中优先级(需要转换)
|
||||
|
||||
6. ✅ 简单的动态 props → CSS 变量(1-2 个)
|
||||
7. ✅ 布尔值 props → 静态样式拆分(2-3 个)
|
||||
|
||||
### 低优先级(复杂优化)
|
||||
|
||||
8. ⚠️ 多个动态 props → CSS 变量(3+ 个)
|
||||
9. ⚠️ 复杂的条件逻辑拆分
|
||||
|
||||
## 📚 参考示例
|
||||
|
||||
### 完整示例 1: 简单组件
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
padding: ${cssVar.padding};
|
||||
color: ${cssVar.colorText};
|
||||
border-radius: ${cssVar.borderRadius};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { cx } from 'antd-style';
|
||||
import { styles } from './style';
|
||||
|
||||
const Component = ({ className }) => {
|
||||
return <div className={cx(styles.root, className)} />;
|
||||
};
|
||||
```
|
||||
|
||||
### 完整示例 2: 带动态 Props
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
width: var(--component-size, 24px);
|
||||
height: var(--component-size, 24px);
|
||||
background: ${cssVar.colorBgContainer};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { cx } from 'antd-style';
|
||||
import { useMemo } from 'react';
|
||||
import { styles } from './style';
|
||||
|
||||
const Component = ({ size = 24, className, style, ...rest }) => {
|
||||
const cssVariables = useMemo<Record<string, string>>(
|
||||
() => ({
|
||||
'--component-size': `${size}px`,
|
||||
}),
|
||||
[size],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.root, className)}
|
||||
style={{
|
||||
...cssVariables,
|
||||
...style,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 完整示例 3: 带 isDarkMode
|
||||
|
||||
**样式文件:**
|
||||
|
||||
```typescript
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
rootDark: css`
|
||||
background: ${cssVar.colorFillTertiary};
|
||||
color: ${cssVar.colorTextLightSolid};
|
||||
`,
|
||||
rootLight: css`
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
color: ${cssVar.colorText};
|
||||
`,
|
||||
}));
|
||||
```
|
||||
|
||||
**组件文件:**
|
||||
|
||||
```typescript
|
||||
import { cx, useTheme } from 'antd-style';
|
||||
import { styles } from './style';
|
||||
|
||||
const Component = ({ className }) => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
theme.isDarkMode ? styles.rootDark : styles.rootLight,
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 🔍 验证步骤
|
||||
|
||||
1. **类型检查:** `pnpm run type-check`
|
||||
2. **运行时测试:** 确保视觉效果一致
|
||||
3. **性能验证:** 检查样式计算是否在模块加载时完成
|
||||
|
||||
## 📊 优化效果
|
||||
|
||||
- ✅ **零运行时开销**:样式在模块加载时计算一次
|
||||
- ✅ **减少重新渲染**:组件不再依赖样式 hook
|
||||
- ✅ **更好的性能**:减少每次渲染的计算开销
|
||||
- ✅ **代码更简洁**:直接导入样式对象
|
||||
|
||||
## 🔧 场景 11: useTheme () → useThemeMode () /cssVar
|
||||
|
||||
**适用:** 组件中只使用 `theme.isDarkMode` 或其他 token 值
|
||||
|
||||
**规则:**
|
||||
|
||||
- 如果只使用 `theme.isDarkMode`,使用 `const { isDarkMode } = useThemeMode()` 替代
|
||||
- 如果使用其他 token(如 `theme.colorText`, `theme.borderRadius` 等),使用 `cssVar` 替代
|
||||
- `useThemeMode()` 比 `useTheme()` 更轻量,只返回 `isDarkMode` 值
|
||||
|
||||
**示例:**
|
||||
|
||||
**之前:**
|
||||
|
||||
```typescript
|
||||
import { useTheme } from 'antd-style';
|
||||
|
||||
const Component = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div className={theme.isDarkMode ? styles.dark : styles.light}>
|
||||
{theme.colorText}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**之后:**
|
||||
|
||||
```typescript
|
||||
import { cssVar, useThemeMode } from 'antd-style';
|
||||
|
||||
const Component = () => {
|
||||
const { isDarkMode } = useThemeMode();
|
||||
return (
|
||||
<div className={isDarkMode ? styles.dark : styles.light}>
|
||||
{cssVar.colorText}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**已优化示例:**
|
||||
|
||||
- `AuroraBackground`, `Select`, `Input`, `Button`, `DatePicker`, `AutoComplete`, `InputNumber`, `InputPassword`, `InputOPT`, `TextArea`, `SpotlightCardItem`, `Spotlight`, `HotkeyInput` - 只使用 `isDarkMode` → `useThemeMode()`
|
||||
- `Image`, `GradientButton`, `Empty`, `FileTypeIcon`, `FormSubmitFooter`, `CodeEditor`, `LobeChat`, `Drawer`, `Modal`, `Avatar`, `AvatarGroup`, `SkeletonAvatar`, `SkeletonButton`, `SkeletonTags`, `Callout`, `LobeHub`, `GridBackground`, `FolderIcon`, `FileIcon`, `TokenTag`, `ChatSendButton`, `AvatarUploader` - 使用 token → `cssVar`
|
||||
|
||||
**无法优化的文件(需要保留 `useTheme()`):**
|
||||
|
||||
- `useMermaid`, `useStreamMermaid`, `useHighlight`, `useStreamHighlight` - 需要完整的 theme 对象传给第三方库
|
||||
- `Alert`, `Tag`, `Menu`, `EmojiPicker` - 需要实际颜色值传给颜色计算函数
|
||||
- `SkeletonTitle`, `SkeletonTags` - 需要数值进行数学运算
|
||||
- `GridShowcase`, `GridBackground/demos` - 需要实际颜色值传给 `rgba()` 函数
|
||||
- `CustomFonts` - 需要实际字符串值进行字符串拼接
|
||||
- `Giscus/style.ts` - 需要实际颜色值传给 `readableColor()` 和 `rgba()` 函数(其他 token 已优化为 `cssVar`)
|
||||
|
||||
**注意事项:**
|
||||
|
||||
- `useThemeMode()` 只返回 `{ isDarkMode }`,不返回完整的 theme 对象
|
||||
- `cssVar` 的值是字符串(如 `"14px"`, `"#ffffff"`),可以直接在 JSX 中使用
|
||||
- 如果 token 需要用于数值计算(如 `Math.round(theme.fontSize * 1.5)`),需要保留 `useTheme()`
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
`createStaticStyles` 迁移是一个渐进式的优化过程。对于简单的静态样式,可以直接转换;对于复杂的动态场景,需要根据具体情况选择合适的优化策略。关键是要理解每种场景的处理方式,并灵活运用 CSS 变量、静态样式拆分等技术。
|
||||
|
||||
### useTheme () 优化总结
|
||||
|
||||
- ✅ **使用 `useThemeMode()`**:当组件只使用 `theme.isDarkMode` 时
|
||||
- ✅ **使用 `cssVar`**:当组件使用其他 token 值(颜色、尺寸等)时
|
||||
- ⚠️ **保留 `useTheme()`**:当 token 需要用于数值计算或传给第三方库时
|
||||
Reference in New Issue
Block a user