TypeScript集成 #

一、基础类型 #

1.1 安装类型定义 #

bash
npm install --save-dev @types/styled-components

1.2 基本类型使用 #

tsx
import styled from 'styled-components';

interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
  padding: ${props => 
    props.size === 'small' ? '8px 16px' :
    props.size === 'large' ? '16px 32px' :
    '12px 24px'
  };
  background: ${props => props.primary ? '#667eea' : '#fff'};
  color: ${props => props.primary ? '#fff' : '#667eea'};
  border: 2px solid #667eea;
  border-radius: 8px;
`;

function App() {
  return (
    <>
      <Button primary>Primary Button</Button>
      <Button size="large">Large Button</Button>
    </>
  );
}

1.3 扩展 HTML 元素属性 #

tsx
import styled from 'styled-components';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  $borderColor?: string;
  $borderRadius?: string;
}

const Input = styled.input<InputProps>`
  padding: 12px 16px;
  border: 2px solid ${props => props.$borderColor || '#e0e0e0'};
  border-radius: ${props => props.$borderRadius || '8px'};
  
  &:focus {
    border-color: #667eea;
    outline: none;
  }
`;

function App() {
  return (
    <Input 
      $borderColor="#667eea" 
      placeholder="Enter text"
      type="text"
    />
  );
}

二、主题类型 #

2.1 扩展 DefaultTheme #

tsx
import 'styled-components';
import { DefaultTheme } from 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    colors: {
      primary: string;
      secondary: string;
      background: string;
      text: string;
      error: string;
      success: string;
    };
    fonts: {
      main: string;
      heading: string;
      code: string;
    };
    spacing: {
      [key: number]: string;
    };
    borderRadius: {
      sm: string;
      md: string;
      lg: string;
    };
    shadows: {
      sm: string;
      md: string;
      lg: string;
    };
  }
}

2.2 定义主题 #

tsx
import { DefaultTheme } from 'styled-components';

const lightTheme: DefaultTheme = {
  colors: {
    primary: '#667eea',
    secondary: '#764ba2',
    background: '#ffffff',
    text: '#333333',
    error: '#ff4d4f',
    success: '#22c55e',
  },
  fonts: {
    main: 'Inter, sans-serif',
    heading: 'Poppins, sans-serif',
    code: 'Fira Code, monospace',
  },
  spacing: {
    1: '0.25rem',
    2: '0.5rem',
    4: '1rem',
    6: '1.5rem',
    8: '2rem',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px',
  },
  shadows: {
    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)',
  },
};

const darkTheme: DefaultTheme = {
  colors: {
    primary: '#818cf8',
    secondary: '#a78bfa',
    background: '#111827',
    text: '#f9fafb',
    error: '#f87171',
    success: '#4ade80',
  },
  fonts: {
    main: 'Inter, sans-serif',
    heading: 'Poppins, sans-serif',
    code: 'Fira Code, monospace',
  },
  spacing: {
    1: '0.25rem',
    2: '0.5rem',
    4: '1rem',
    6: '1.5rem',
    8: '2rem',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px',
  },
  shadows: {
    sm: '0 1px 2px rgba(0, 0, 0, 0.2)',
    md: '0 4px 6px rgba(0, 0, 0, 0.3)',
    lg: '0 10px 15px rgba(0, 0, 0, 0.4)',
  },
};

2.3 使用主题 #

tsx
import styled, { ThemeProvider, useTheme } from 'styled-components';

const Button = styled.button`
  padding: ${props => props.theme.spacing[2]} ${props => props.theme.spacing[4]};
  background: ${props => props.theme.colors.primary};
  color: white;
  border: none;
  border-radius: ${props => props.theme.borderRadius.md};
`;

const Card = styled.div`
  padding: ${props => props.theme.spacing[6]};
  background: ${props => props.theme.colors.background};
  border-radius: ${props => props.theme.borderRadius.lg};
  box-shadow: ${props => props.theme.shadows.md};
`;

function App() {
  return (
    <ThemeProvider theme={lightTheme}>
      <Card>
        <Button>Themed Button</Button>
      </Card>
    </ThemeProvider>
  );
}

三、泛型组件 #

3.1 泛型样式组件 #

tsx
import styled from 'styled-components';

interface FlexProps<T = {}> {
  direction?: 'row' | 'column';
  align?: 'flex-start' | 'center' | 'flex-end' | 'stretch';
  justify?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around';
  gap?: string;
}

const Flex = styled.div<FlexProps>`
  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 || '0'};
`;

function App() {
  return (
    <Flex direction="column" align="center" gap="16px">
      <div>Item 1</div>
      <div>Item 2</div>
    </Flex>
  );
}

3.2 泛型变体组件 #

tsx
import styled, { css } from 'styled-components';

type Variant = 'primary' | 'secondary' | 'danger';
type Size = 'small' | 'medium' | 'large';

interface ButtonProps {
  $variant?: Variant;
  $size?: Size;
  $fullWidth?: boolean;
}

const variantStyles: Record<Variant, ReturnType<typeof css>> = {
  primary: css`
    background: #667eea;
    color: white;
    border: none;
  `,
  secondary: css`
    background: transparent;
    color: #667eea;
    border: 2px solid #667eea;
  `,
  danger: css`
    background: #ff4d4f;
    color: white;
    border: none;
  `,
};

const sizeStyles: Record<Size, ReturnType<typeof css>> = {
  small: css`
    padding: 8px 16px;
    font-size: 14px;
  `,
  medium: css`
    padding: 12px 24px;
    font-size: 16px;
  `,
  large: css`
    padding: 16px 32px;
    font-size: 18px;
  `,
};

const Button = styled.button<ButtonProps>`
  ${props => variantStyles[props.$variant || 'primary']}
  ${props => sizeStyles[props.$size || 'medium']}
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s;
  
  ${props => props.$fullWidth && css`
    width: 100%;
  `}
`;

四、类型工具 #

4.1 Props 类型提取 #

tsx
import styled from 'styled-components';

type StyledProps<T> = T extends styled.ComponentType<infer P> ? P : never;

const Button = styled.button<{ $primary?: boolean }>`
  background: ${props => props.$primary ? '#667eea' : '#fff'};
`;

type ButtonProps = StyledProps<typeof Button>;

4.2 主题类型工具 #

tsx
import { DefaultTheme, useTheme } from 'styled-components';

type ThemeColor = keyof DefaultTheme['colors'];
type ThemeSpacing = keyof DefaultTheme['spacing'];

function useThemeColor(color: ThemeColor): string {
  const theme = useTheme();
  return theme.colors[color];
}

function useThemeSpacing(spacing: ThemeSpacing): string {
  const theme = useTheme();
  return theme.spacing[spacing];
}

const ColoredBox = styled.div<{ $color: ThemeColor }>`
  background: ${props => props.theme.colors[props.$color]};
`;

4.3 条件类型样式 #

tsx
import styled, { css } from 'styled-components';

type ResponsiveValue<T> = {
  base: T;
  sm?: T;
  md?: T;
  lg?: T;
  xl?: T;
};

interface ResponsiveProps {
  $padding?: ResponsiveValue<string>;
  $fontSize?: ResponsiveValue<string>;
}

const applyResponsive = <T,>(
  property: string,
  value: ResponsiveValue<T> | undefined
) => css`
  ${value && css`
    ${property}: ${value.base};
    
    ${value.sm && css`@media (min-width: 640px) { ${property}: ${value.sm}; }`}
    ${value.md && css`@media (min-width: 768px) { ${property}: ${value.md}; }`}
    ${value.lg && css`@media (min-width: 1024px) { ${property}: ${value.lg}; }`}
    ${value.xl && css`@media (min-width: 1280px) { ${property}: ${value.xl}; }`}
  `}
`;

const Box = styled.div<ResponsiveProps>`
  ${props => applyResponsive('padding', props.$padding)}
  ${props => applyResponsive('font-size', props.$fontSize)}
`;

五、类型安全模式 #

5.1 类型安全的变体 #

tsx
import styled, { css } from 'styled-components';

interface VariantConfig<T extends string> {
  variants: Record<T, ReturnType<typeof css>>;
  defaultVariant: T;
}

function createVariantStyles<T extends string>(
  config: VariantConfig<T>
): (variant?: T) => ReturnType<typeof css> {
  return (variant) => config.variants[variant || config.defaultVariant];
}

const buttonVariants = createVariantStyles({
  variants: {
    primary: css`background: #667eea; color: white;`,
    secondary: css`background: transparent; color: #667eea;`,
    danger: css`background: #ff4d4f; color: white;`,
  },
  defaultVariant: 'primary',
});

const Button = styled.button<{ $variant?: 'primary' | 'secondary' | 'danger' }>`
  ${props => buttonVariants(props.$variant)}
  padding: 12px 24px;
  border-radius: 8px;
`;

5.2 类型安全的属性 #

tsx
import styled from 'styled-components';

type CSSPropertyName = 
  | 'padding'
  | 'margin'
  | 'fontSize'
  | 'fontWeight'
  | 'color'
  | 'background'
  | 'borderRadius';

type StyleProps = Partial<Record<CSSPropertyName, string>>;

const Box = styled.div<StyleProps>`
  ${props => props.padding && `padding: ${props.padding};`}
  ${props => props.margin && `margin: ${props.margin};`}
  ${props => props.fontSize && `font-size: ${props.fontSize};`}
  ${props => props.fontWeight && `font-weight: ${props.fontWeight};`}
  ${props => props.color && `color: ${props.color};`}
  ${props => props.background && `background: ${props.background};`}
  ${props => props.borderRadius && `border-radius: ${props.borderRadius};`}
`;

六、类型声明文件 #

6.1 全局类型声明 #

tsx
import 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    colors: {
      primary: string;
      secondary: string;
      background: string;
      text: string;
    };
    spacing: Record<number, string>;
    borderRadius: Record<string, string>;
  }
}

declare module 'styled-components' {
  export function css(
    first: TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
  ): FlattenedInterpolation<object>;
}

6.2 组件类型导出 #

tsx
import styled from 'styled-components';

export interface ButtonProps {
  $variant?: 'primary' | 'secondary' | 'danger';
  $size?: 'small' | 'medium' | 'large';
  $disabled?: boolean;
  $loading?: boolean;
}

export const Button = styled.button<ButtonProps>`
  padding: 12px 24px;
  border-radius: 8px;
  cursor: pointer;
  
  ${props => props.$disabled && css`
    opacity: 0.6;
    cursor: not-allowed;
  `}
`;

七、最佳实践 #

7.1 使用 transient props #

tsx
import styled from 'styled-components';

interface ButtonProps {
  $variant?: 'primary' | 'secondary';
  $size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
  padding: ${props => props.$size === 'large' ? '16px 32px' : '12px 24px'};
  background: ${props => props.$variant === 'primary' ? '#667eea' : '#fff'};
`;

7.2 类型安全的主题访问 #

tsx
import styled, { useTheme } from 'styled-components';

const getColor = (theme: DefaultTheme, color: keyof DefaultTheme['colors']): string => {
  return theme.colors[color];
};

const Button = styled.button`
  background: ${props => getColor(props.theme, 'primary')};
`;

function Component() {
  const theme = useTheme();
  const primaryColor = getColor(theme, 'primary');
  
  return <div style={{ color: primaryColor }}>Colored text</div>;
}

八、总结 #

TypeScript 集成要点速查表:

要点 说明
安装类型 @types/styled-components
Props 类型 styled.button<Props>
扩展属性 extends React.ButtonHTMLAttributes
主题类型 扩展 DefaultTheme
transient props $ 前缀避免 DOM 传递
泛型组件 <T extends ...>

下一步:学习 测试策略 掌握组件样式的测试方法。

最后更新:2026-03-28