样式复用 #

一、混入模式 #

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