样式复用 #
一、混入模式 #
1.1 基础混入 #
jsx
import styled, { css } from 'styled-components';
const flexCenter = css`
display: flex;
align-items: center;
justify-content: center;
`;
const cardBase = css`
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
`;
const smoothTransition = css`
transition: all 0.3s ease;
`;
const Container = styled.div`
${flexCenter}
${smoothTransition}
min-height: 100vh;
`;
const Card = styled.div`
${cardBase}
${smoothTransition}
padding: 24px;
`;
1.2 参数化混入 #
jsx
const padding = (size) => css`
padding: ${{
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
}[size] || size};
`;
const margin = (size) => css`
margin: ${{
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
}[size] || size};
`;
const borderRadius = (size) => css`
border-radius: ${{
sm: '4px',
md: '8px',
lg: '12px',
xl: '16px',
full: '9999px',
}[size] || size};
`;
const Box = styled.div`
${padding('lg')}
${margin('md')}
${borderRadius('lg')}
background: white;
`;
1.3 条件混入 #
jsx
const elevated = (level = 'md') => css`
box-shadow: ${{
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px rgba(0, 0, 0, 0.15)',
}[level]};
`;
const interactive = css`
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
`;
const Card = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
${props => props.$elevated && elevated(props.$elevation)}
${props => props.$interactive && interactive}
`;
二、样式片段库 #
2.1 布局片段 #
jsx
import styled, { css } from 'styled-components';
const layout = {
flexCenter: css`
display: flex;
align-items: center;
justify-content: center;
`,
flexBetween: css`
display: flex;
align-items: center;
justify-content: space-between;
`,
flexColumn: css`
display: flex;
flex-direction: column;
`,
absoluteCenter: css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`,
fullScreen: css`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
`,
aspectRatio: (ratio) => css`
aspect-ratio: ${ratio};
@supports not (aspect-ratio: ${ratio}) {
&::before {
content: '';
display: block;
padding-top: calc(100% / ${ratio});
}
}
`,
};
const CenteredBox = styled.div`
${layout.flexCenter}
width: 100%;
height: 100vh;
`;
const Header = styled.header`
${layout.flexBetween}
padding: 16px 24px;
`;
2.2 排版片段 #
jsx
const typography = {
truncate: css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
lineClamp: (lines) => css`
display: -webkit-box;
-webkit-line-clamp: ${lines};
-webkit-box-orient: vertical;
overflow: hidden;
`,
heading: css`
font-family: 'Poppins', sans-serif;
font-weight: 700;
line-height: 1.2;
`,
body: css`
font-family: 'Inter', sans-serif;
line-height: 1.6;
`,
code: css`
font-family: 'Fira Code', monospace;
font-size: 0.9em;
background: #f5f5f5;
padding: 2px 6px;
border-radius: 4px;
`,
};
const Title = styled.h1`
${typography.heading}
${typography.truncate}
font-size: 32px;
`;
const Description = styled.p`
${typography.body}
${typography.lineClamp(3)}
color: #666;
`;
2.3 视觉效果片段 #
jsx
const effects = {
smoothTransition: css`
transition: all 0.3s ease;
`,
hoverLift: css`
&:hover {
transform: translateY(-4px);
}
`,
hoverScale: css`
&:hover {
transform: scale(1.05);
}
`,
focusRing: css`
&:focus-visible {
outline: 2px solid #667eea;
outline-offset: 2px;
}
`,
gradientText: (from, to) => css`
background: linear-gradient(135deg, ${from}, ${to});
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
`,
glass: css`
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
`,
};
const InteractiveCard = styled.div`
padding: 24px;
background: white;
border-radius: 12px;
${effects.smoothTransition}
${effects.hoverLift}
&:hover {
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
`;
三、设计 Token #
3.1 Token 定义 #
jsx
const tokens = {
colors: {
brand: {
primary: '#667eea',
secondary: '#764ba2',
},
semantic: {
success: '#22c55e',
warning: '#f59e0b',
danger: '#ef4444',
info: '#3b82f6',
},
neutral: {
white: '#ffffff',
black: '#000000',
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
},
},
},
spacing: {
0: '0',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
10: '2.5rem',
12: '3rem',
16: '4rem',
20: '5rem',
24: '6rem',
},
typography: {
fontFamily: {
sans: 'Inter, sans-serif',
serif: 'Georgia, serif',
mono: 'Fira Code, monospace',
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
lineHeight: {
tight: '1.25',
normal: '1.5',
relaxed: '1.75',
},
},
borderRadius: {
none: '0',
sm: '0.25rem',
base: '0.5rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
full: '9999px',
},
shadows: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
base: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
},
transitions: {
fast: '150ms',
normal: '300ms',
slow: '500ms',
},
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
};
3.2 Token 使用 #
jsx
import styled from 'styled-components';
const Button = styled.button`
padding: ${tokens.spacing[2]} ${tokens.spacing[4]};
font-family: ${tokens.typography.fontFamily.sans};
font-size: ${tokens.typography.fontSize.base};
font-weight: ${tokens.typography.fontWeight.semibold};
background: ${tokens.colors.brand.primary};
color: ${tokens.colors.neutral.white};
border: none;
border-radius: ${tokens.borderRadius.lg};
box-shadow: ${tokens.shadows.md};
transition: all ${tokens.transitions.normal};
&:hover {
background: #5a6fd6;
}
`;
const Card = styled.div`
padding: ${tokens.spacing[6]};
background: ${tokens.colors.neutral.white};
border-radius: ${tokens.borderRadius['2xl']};
box-shadow: ${tokens.shadows.lg};
`;
3.3 Token 工具函数 #
jsx
const getColor = (path) => {
const parts = path.split('.');
let value = tokens.colors;
for (const part of parts) {
value = value[part];
}
return value;
};
const getSpacing = (size) => tokens.spacing[size] || tokens.spacing[4];
const getFontSize = (size) => tokens.typography.fontSize[size] || tokens.typography.fontSize.base;
const getShadow = (size) => tokens.shadows[size] || tokens.shadows.base;
const Button = styled.button`
padding: ${getSpacing(2)} ${getSpacing(4)};
font-size: ${getFontSize('base')};
background: ${getColor('brand.primary')};
box-shadow: ${getShadow('md')};
`;
四、组件库构建 #
4.1 基础组件 #
jsx
import styled, { css } from 'styled-components';
const Box = styled.div`
padding: ${props => props.$p && tokens.spacing[props.$p]};
margin: ${props => props.$m && tokens.spacing[props.$m]};
background: ${props => props.$bg};
color: ${props => props.$color};
border-radius: ${props => props.$radius && tokens.borderRadius[props.$radius]};
box-shadow: ${props => props.$shadow && tokens.shadows[props.$shadow]};
`;
const Flex = styled(Box)`
display: flex;
flex-direction: ${props => props.$direction || 'row'};
align-items: ${props => props.$align || 'stretch'};
justify-content: ${props => props.$justify || 'flex-start'};
gap: ${props => props.$gap && tokens.spacing[props.$gap]};
flex-wrap: ${props => props.$wrap || 'nowrap'};
`;
const Stack = styled(Flex)`
flex-direction: column;
`;
const Grid = styled(Box)`
display: grid;
grid-template-columns: ${props => props.$cols ? `repeat(${props.$cols}, 1fr)` : '1fr'};
gap: ${props => props.$gap && tokens.spacing[props.$gap]};
`;
4.2 变体组件 #
jsx
const buttonVariants = {
solid: css`
background: ${props => props.$color || tokens.colors.brand.primary};
color: white;
border: none;
`,
outline: css`
background: transparent;
color: ${props => props.$color || tokens.colors.brand.primary};
border: 2px solid ${props => props.$color || tokens.colors.brand.primary};
`,
ghost: css`
background: transparent;
color: ${props => props.$color || tokens.colors.brand.primary};
border: none;
&:hover {
background: rgba(102, 126, 234, 0.1);
}
`,
};
const buttonSizes = {
sm: css`
padding: ${tokens.spacing[1]} ${tokens.spacing[3]};
font-size: ${tokens.typography.fontSize.sm};
`,
md: css`
padding: ${tokens.spacing[2]} ${tokens.spacing[4]};
font-size: ${tokens.typography.fontSize.base};
`,
lg: css`
padding: ${tokens.spacing[3]} ${tokens.spacing[6]};
font-size: ${tokens.typography.fontSize.lg};
`,
};
const Button = styled.button`
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: ${tokens.typography.fontWeight.semibold};
border-radius: ${tokens.borderRadius.lg};
cursor: pointer;
transition: all ${tokens.transitions.normal};
${props => buttonVariants[props.$variant || 'solid']}
${props => buttonSizes[props.$size || 'md']}
${props => props.$fullWidth && css`
width: 100%;
`}
${props => props.$rounded && css`
border-radius: ${tokens.borderRadius.full};
`}
&:hover:not(:disabled) {
opacity: 0.9;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
4.3 复合组件 #
jsx
const Card = styled.div`
background: ${tokens.colors.neutral.white};
border-radius: ${tokens.borderRadius['2xl']};
box-shadow: ${tokens.shadows.lg};
overflow: hidden;
`;
Card.Header = styled.div`
padding: ${tokens.spacing[4]} ${tokens.spacing[6]};
border-bottom: 1px solid ${tokens.colors.neutral.gray[200]};
`;
Card.Body = styled.div`
padding: ${tokens.spacing[6]};
`;
Card.Footer = styled.div`
padding: ${tokens.spacing[4]} ${tokens.spacing[6]};
background: ${tokens.colors.neutral.gray[50]};
border-top: 1px solid ${tokens.colors.neutral.gray[200]};
`;
Card.Image = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
`;
五、主题集成 #
5.1 Token 作为主题 #
jsx
import styled, { ThemeProvider } from 'styled-components';
const theme = {
...tokens,
colors: {
...tokens.colors,
background: tokens.colors.neutral.white,
text: tokens.colors.neutral.gray[900],
primary: tokens.colors.brand.primary,
},
};
const Button = styled.button`
padding: ${props => props.theme.spacing[2]} ${props => props.theme.spacing[4]};
background: ${props => props.theme.colors.primary};
color: ${props => props.theme.colors.neutral.white};
`;
function App() {
return (
<ThemeProvider theme={theme}>
<Button>Themed Button</Button>
</ThemeProvider>
);
}
六、总结 #
样式复用技巧速查表:
| 技术 | 用途 | 示例 |
|---|---|---|
| 混入 | 复用样式片段 | const mixin = css\…`` |
| 参数化混入 | 动态样式 | padding('lg') |
| 样式片段库 | 组织复用 | layout.flexCenter |
| 设计 Token | 设计系统 | tokens.colors.brand.primary |
| 基础组件 | 原子组件 | Box, Flex, Stack |
| 变体组件 | 样式变体 | buttonVariants.solid |
下一步:学习 性能优化 掌握渲染优化技巧。
最后更新:2026-03-28