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