Props动态样式 #

基本概念 #

动态样式是 CSS-in-JS 的核心优势之一。Emotion 允许你根据组件的 props、state 或主题动态生成样式,实现高度可定制的组件。

基本用法 #

函数返回样式值 #

在模板字符串中使用函数获取 props:

jsx
import styled from '@emotion/styled'

const Button = styled.button`
  padding: 10px 20px;
  background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`

function App() {
  return (
    <>
      <Button primary>Primary Button</Button>
      <Button>Default Button</Button>
    </>
  )
}

对象语法动态样式 #

使用对象语法定义动态样式:

jsx
import styled from '@emotion/styled'

const Box = styled.div(props => ({
  padding: props.padding || '16px',
  margin: props.margin || '0',
  backgroundColor: props.bg || '#f5f5f5',
  borderRadius: props.rounded ? '8px' : '0',
}))

function App() {
  return (
    <>
      <Box padding="24px" bg="#e3f2fd">Custom Box</Box>
      <Box rounded>Default Rounded Box</Box>
    </>
  )
}

条件样式 #

三元表达式 #

使用三元表达式处理简单条件:

jsx
import styled from '@emotion/styled'

const Input = styled.input`
  padding: 12px;
  border: 2px solid ${props => props.error ? '#dc3545' : '#ced4da'};
  border-radius: 4px;
  outline: none;
  
  &:focus {
    border-color: ${props => props.error ? '#dc3545' : '#007bff'};
  }
`

function Form() {
  const [value, setValue] = useState('')
  const error = value.length > 0 && value.length < 3
  
  return (
    <Input
      value={value}
      onChange={e => setValue(e.target.value)}
      error={error}
      placeholder="Enter at least 3 characters"
    />
  )
}

Switch/Map 模式 #

使用映射对象处理多种情况:

jsx
import styled from '@emotion/styled'

const colorMap = {
  primary: { bg: '#007bff', text: '#fff' },
  secondary: { bg: '#6c757d', text: '#fff' },
  success: { bg: '#28a745', text: '#fff' },
  danger: { bg: '#dc3545', text: '#fff' },
  warning: { bg: '#ffc107', text: '#333' },
  info: { bg: '#17a2b8', text: '#fff' },
}

const Badge = styled.span`
  display: inline-block;
  padding: 4px 8px;
  font-size: 12px;
  font-weight: bold;
  border-radius: 4px;
  
  background-color: ${props => colorMap[props.variant]?.bg || colorMap.secondary.bg};
  color: ${props => colorMap[props.variant]?.text || colorMap.secondary.text};
`

function App() {
  return (
    <>
      <Badge variant="primary">Primary</Badge>
      <Badge variant="success">Success</Badge>
      <Badge variant="warning">Warning</Badge>
      <Badge variant="danger">Danger</Badge>
    </>
  )
}

复杂条件逻辑 #

提取样式函数处理复杂逻辑:

jsx
import styled from '@emotion/styled'

const getButtonStyles = (props) => {
  const baseStyles = `
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-weight: bold;
    transition: all 0.2s ease;
  `
  
  const sizeStyles = {
    small: 'padding: 6px 12px; font-size: 12px;',
    medium: 'padding: 10px 20px; font-size: 14px;',
    large: 'padding: 14px 28px; font-size: 16px;',
  }
  
  const variantStyles = {
    solid: `
      background-color: ${props.color || '#007bff'};
      color: white;
      
      &:hover {
        opacity: 0.9;
      }
    `,
    outline: `
      background-color: transparent;
      border: 2px solid ${props.color || '#007bff'};
      color: ${props.color || '#007bff'};
      
      &:hover {
        background-color: ${props.color || '#007bff'};
        color: white;
      }
    `,
    ghost: `
      background-color: transparent;
      color: ${props.color || '#007bff'};
      
      &:hover {
        background-color: rgba(0, 123, 255, 0.1);
      }
    `,
  }
  
  return `
    ${baseStyles}
    ${sizeStyles[props.size || 'medium']}
    ${variantStyles[props.variant || 'solid']}
    ${props.fullWidth ? 'width: 100%;' : ''}
    ${props.disabled ? 'opacity: 0.5; cursor: not-allowed;' : ''}
  `
}

const Button = styled.button`
  ${props => getButtonStyles(props)}
`

function App() {
  return (
    <>
      <Button variant="solid" size="large">Solid Large</Button>
      <Button variant="outline" size="medium" color="#28a745">Outline Green</Button>
      <Button variant="ghost" size="small">Ghost Small</Button>
      <Button fullWidth>Full Width</Button>
      <Button disabled>Disabled</Button>
    </>
  )
}

组合动态样式 #

样式函数组合 #

创建可复用的样式函数:

jsx
import styled from '@emotion/styled'
import { css } from '@emotion/react'

const withSize = (props) => {
  const sizes = {
    small: css`padding: 4px 8px; font-size: 12px;`,
    medium: css`padding: 8px 16px; font-size: 14px;`,
    large: css`padding: 12px 24px; font-size: 16px;`,
  }
  return sizes[props.size] || sizes.medium
}

const withVariant = (props) => {
  const variants = {
    primary: css`background: #007bff; color: white;`,
    secondary: css`background: #6c757d; color: white;`,
    danger: css`background: #dc3545; color: white;`,
  }
  return variants[props.variant] || variants.primary
}

const withDisabled = (props) => props.disabled && css`
  opacity: 0.5;
  cursor: not-allowed;
`

const Button = styled.button`
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  ${withSize}
  ${withVariant}
  ${withDisabled}
  
  &:hover {
    opacity: 0.9;
  }
`

条件样式数组 #

使用数组组合条件样式:

jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const baseStyle = css`
  padding: 12px;
  border-radius: 4px;
  transition: all 0.2s ease;
`

const activeStyle = css`
  background-color: #007bff;
  color: white;
`

const disabledStyle = css`
  opacity: 0.5;
  cursor: not-allowed;
`

const largeStyle = css`
  padding: 16px 24px;
  font-size: 18px;
`

function Button({ active, disabled, large, children }) {
  return (
    <button
      css={[
        baseStyle,
        active && activeStyle,
        disabled && disabledStyle,
        large && largeStyle,
      ]}
      disabled={disabled}
    >
      {children}
    </button>
  )
}

主题集成 #

使用 ThemeProvider #

jsx
import styled from '@emotion/styled'
import { ThemeProvider } from '@emotion/react'

const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
  },
  fontSizes: {
    small: '12px',
    medium: '14px',
    large: '16px',
    xlarge: '20px',
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  borderRadius: {
    small: '4px',
    medium: '8px',
    large: '12px',
  },
}

const Button = styled.button`
  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
  background-color: ${props => props.theme.colors[props.color] || props.theme.colors.primary};
  color: white;
  border: none;
  border-radius: ${props => props.theme.borderRadius.small};
  font-size: ${props => props.theme.fontSizes.medium};
  cursor: pointer;
  
  &:hover {
    opacity: 0.9;
  }
`

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button color="primary">Primary</Button>
      <Button color="success">Success</Button>
      <Button color="danger">Danger</Button>
    </ThemeProvider>
  )
}

useTheme Hook #

在组件中访问主题:

jsx
/** @jsxImportSource @emotion/react */
import { css, useTheme } from '@emotion/react'

function ThemedCard({ children }) {
  const theme = useTheme()
  
  return (
    <div
      css={css`
        padding: ${theme.spacing.md};
        background: white;
        border-radius: ${theme.borderRadius.medium};
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        
        &:hover {
          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
        }
      `}
    >
      {children}
    </div>
  )
}

动画与过渡 #

动态过渡效果 #

jsx
import styled from '@emotion/styled'

const AnimatedBox = styled.div`
  width: 100px;
  height: 100px;
  background-color: ${props => props.color || '#007bff'};
  border-radius: ${props => props.rounded ? '50%' : '8px'};
  transition: all ${props => props.duration || '0.3s'} ease;
  
  &:hover {
    transform: ${props => props.scale ? `scale(${props.scale})` : 'scale(1.1)'};
    background-color: ${props => props.hoverColor || '#0056b3'};
  }
`

function App() {
  return (
    <>
      <AnimatedBox color="#28a745" hoverColor="#1e7e34" rounded duration="0.5s" />
      <AnimatedBox color="#dc3545" scale="1.2" />
    </>
  )
}

状态切换动画 #

jsx
import styled from '@emotion/styled'
import { useState } from 'react'

const ToggleContainer = styled.div`
  width: 60px;
  height: 30px;
  background-color: ${props => props.isOn ? '#28a745' : '#ccc'};
  border-radius: 15px;
  padding: 3px;
  cursor: pointer;
  transition: background-color 0.3s ease;
`

const ToggleCircle = styled.div`
  width: 24px;
  height: 24px;
  background-color: white;
  border-radius: 50%;
  transition: transform 0.3s ease;
  transform: translateX(${props => props.isOn ? '30px' : '0'});
`

function Toggle() {
  const [isOn, setIsOn] = useState(false)
  
  return (
    <ToggleContainer isOn={isOn} onClick={() => setIsOn(!isOn)}>
      <ToggleCircle isOn={isOn} />
    </ToggleContainer>
  )
}

响应式样式 #

媒体查询 #

jsx
import styled from '@emotion/styled'

const ResponsiveGrid = styled.div`
  display: grid;
  gap: 16px;
  
  grid-template-columns: 1fr;
  
  @media (min-width: 576px) {
    grid-template-columns: repeat(2, 1fr);
  }
  
  @media (min-width: 768px) {
    grid-template-columns: repeat(3, 1fr);
  }
  
  @media (min-width: 992px) {
    grid-template-columns: repeat(4, 1fr);
  }
`

动态媒体查询 #

jsx
import styled from '@emotion/styled'

const breakpoints = {
  sm: '576px',
  md: '768px',
  lg: '992px',
  xl: '1200px',
}

const ResponsiveText = styled.p`
  font-size: 14px;
  
  @media (min-width: ${breakpoints.md}) {
    font-size: ${props => props.mdSize || '16px'};
  }
  
  @media (min-width: ${breakpoints.lg}) {
    font-size: ${props => props.lgSize || '18px'};
  }
`

最佳实践 #

1. 默认值处理 #

始终为 props 提供默认值:

jsx
const Button = styled.button`
  padding: ${props => props.padding || '10px 20px'};
  background: ${props => props.background || '#007bff'};
`

2. 类型安全 #

使用 TypeScript 定义 props 类型:

tsx
import styled from '@emotion/styled'

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger'
  size?: 'small' | 'medium' | 'large'
  fullWidth?: boolean
}

const Button = styled.button<ButtonProps>`
  padding: ${props => props.size === 'large' ? '14px 28px' : '10px 20px'};
  background: ${props => {
    switch (props.variant) {
      case 'primary': return '#007bff'
      case 'secondary': return '#6c757d'
      case 'danger': return '#dc3545'
      default: return '#007bff'
    }
  }};
  width: ${props => props.fullWidth ? '100%' : 'auto'};
`

3. 避免过度动态 #

不要过度使用动态样式,保持可读性:

jsx
const Button = styled.button`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
  color: white;
`

下一步 #

掌握了动态样式后,继续学习 嵌套与选择器,了解 Emotion 中的 CSS 嵌套和选择器用法。

最后更新:2026-03-28