属性传递 #
一、attrs 基础 #
1.1 什么是 attrs #
attrs 方法允许你在样式组件上预设置 HTML 属性:
jsx
import styled from 'styled-components';
const Input = styled.input.attrs({
type: 'text',
placeholder: 'Enter text...',
})`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
`;
function App() {
return <Input />;
}
渲染结果:
html
<input type="text" placeholder="Enter text..." class="sc-xxx" />
1.2 attrs 与 styled 链式调用 #
jsx
const Input = styled.input
.attrs({ type: 'text' })`
padding: 12px;
border: 2px solid #e0e0e0;
`;
二、静态属性 #
2.1 设置固定属性 #
jsx
const EmailInput = styled.input.attrs({
type: 'email',
autoComplete: 'email',
})`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
`;
const PasswordInput = styled.input.attrs({
type: 'password',
autoComplete: 'current-password',
})`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
`;
const NumberInput = styled.input.attrs({
type: 'number',
min: 0,
max: 100,
step: 1,
})`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
width: 100px;
`;
2.2 设置 ARIA 属性 #
jsx
const Button = styled.button.attrs({
role: 'button',
'aria-label': 'Click to submit',
})`
padding: 12px 24px;
background: #667eea;
color: white;
border-radius: 8px;
`;
const IconButton = styled.button.attrs({
'aria-hidden': 'true',
})`
width: 40px;
height: 40px;
border-radius: 50%;
background: #f0f0f0;
border: none;
cursor: pointer;
`;
2.3 设置 data 属性 #
jsx
const Card = styled.div.attrs({
'data-testid': 'card',
})`
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
`;
const ListItem = styled.li.attrs({
'data-role': 'list-item',
})`
padding: 12px;
border-bottom: 1px solid #eee;
`;
三、动态属性 #
3.1 基于 Props 的动态属性 #
jsx
const Input = styled.input.attrs(props => ({
type: props.type || 'text',
placeholder: props.placeholder || 'Enter value',
disabled: props.disabled,
}))`
padding: 12px 16px;
border: 2px solid ${props => props.disabled ? '#ccc' : '#e0e0e0'};
border-radius: 8px;
background: ${props => props.disabled ? '#f5f5f5' : 'white'};
`;
function App() {
return (
<>
<Input placeholder="Name" />
<Input type="email" placeholder="Email" />
<Input disabled placeholder="Disabled" />
</>
);
}
3.2 计算属性 #
jsx
const Input = styled.input.attrs(props => ({
type: props.type || 'text',
maxLength: props.maxLength || 100,
minLength: props.minLength || 0,
}))`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
width: ${props => props.fullWidth ? '100%' : 'auto'};
`;
const RangeInput = styled.input.attrs(props => ({
type: 'range',
min: props.min || 0,
max: props.max || 100,
step: props.step || 1,
defaultValue: props.defaultValue || 50,
}))`
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
outline: none;
`;
3.3 条件属性 #
jsx
const Button = styled.button.attrs(props => ({
disabled: props.loading || props.disabled,
'aria-busy': props.loading,
type: props.submit ? 'submit' : 'button',
}))`
padding: 12px 24px;
background: ${props => props.loading ? '#ccc' : '#667eea'};
color: white;
border-radius: 8px;
cursor: ${props => props.loading ? 'wait' : 'pointer'};
opacity: ${props => props.disabled ? 0.6 : 1};
`;
function App() {
return (
<>
<Button>Normal Button</Button>
<Button loading>Loading...</Button>
<Button submit>Submit Form</Button>
<Button disabled>Disabled</Button>
</>
);
}
四、属性与样式联动 #
4.1 属性值用于样式 #
jsx
const Input = styled.input.attrs(props => ({
size: props.$size || 20,
}))`
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
width: ${props => props.size}ch;
`;
const Progress = styled.progress.attrs(props => ({
max: props.max || 100,
value: props.value || 0,
}))`
width: 100%;
height: 8px;
border-radius: 4px;
&::-webkit-progress-bar {
background: #e0e0e0;
border-radius: 4px;
}
&::-webkit-progress-value {
background: #667eea;
border-radius: 4px;
}
`;
4.2 属性默认值 #
jsx
const Input = styled.input.attrs(props => ({
type: props.type || 'text',
placeholder: props.placeholder || `Enter ${props.name || 'value'}`,
required: props.required ?? true,
}))`
padding: 12px 16px;
border: 2px solid ${props => props.required ? '#ffccc7' : '#e0e0e0'};
border-radius: 8px;
`;
function App() {
return (
<form>
<Input name="username" placeholder="Username" />
<Input name="email" type="email" />
<Input name="bio" required={false} />
</form>
);
}
五、类型安全 #
5.1 TypeScript 类型定义 #
tsx
import styled from 'styled-components';
interface InputProps {
$size?: 'small' | 'medium' | 'large';
type?: string;
placeholder?: string;
}
const Input = styled.input.attrs<InputProps>(props => ({
type: props.type || 'text',
placeholder: props.placeholder || 'Enter value',
}))`
padding: ${props =>
props.$size === 'small' ? '8px 12px' :
props.$size === 'large' ? '16px 24px' :
'12px 16px'
};
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: ${props =>
props.$size === 'small' ? '14px' :
props.$size === 'large' ? '18px' :
'16px'
};
`;
5.2 扩展 HTML 属性 #
tsx
import styled from 'styled-components';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
$variant?: 'primary' | 'secondary' | 'danger';
$loading?: boolean;
}
const Button = styled.button.attrs<ButtonProps>(props => ({
disabled: props.$loading || props.disabled,
type: props.type || 'button',
}))`
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
cursor: ${props => props.$loading ? 'wait' : 'pointer'};
${props => props.$variant === 'primary' && `
background: #667eea;
color: white;
border: none;
`}
${props => props.$variant === 'secondary' && `
background: transparent;
color: #667eea;
border: 2px solid #667eea;
`}
${props => props.$variant === 'danger' && `
background: #ff4d4f;
color: white;
border: none;
`}
${props => props.$loading && `
opacity: 0.7;
`}
`;
六、高级用法 #
6.1 多次链式调用 #
jsx
const Input = styled.input
.attrs({ type: 'text' })
.attrs(props => ({
placeholder: props.placeholder || 'Enter text',
}))
.attrs(props => ({
disabled: props.disabled,
}))`
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
`;
6.2 属性合并 #
jsx
const BaseInput = styled.input.attrs({
autoComplete: 'off',
spellCheck: 'false',
})`
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
`;
const EmailInput = styled(BaseInput).attrs({
type: 'email',
autoComplete: 'email',
})`
background: #f0f7ff;
`;
const PasswordInput = styled(BaseInput).attrs({
type: 'password',
autoComplete: 'current-password',
})`
background: #fff5f5;
`;
6.3 属性工厂函数 #
jsx
const createInput = (type, defaultProps = {}) => styled.input.attrs({
type,
...defaultProps,
})`
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
`;
const TextInput = createInput('text', {
placeholder: 'Enter text',
});
const EmailInput = createInput('email', {
placeholder: 'Enter email',
autoComplete: 'email',
});
const NumberInput = createInput('number', {
min: 0,
max: 100,
});
七、实际应用场景 #
7.1 表单组件 #
jsx
import styled from 'styled-components';
const FormInput = styled.input.attrs(props => ({
type: props.type || 'text',
required: props.required,
'aria-label': props['aria-label'] || props.name,
}))`
width: 100%;
padding: 12px 16px;
border: 2px solid ${props => props.error ? '#ff4d4f' : '#e0e0e0'};
border-radius: 8px;
font-size: 16px;
&:focus {
border-color: #667eea;
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
&::placeholder {
color: #aaa;
}
`;
const FormTextarea = styled.textarea.attrs(props => ({
rows: props.rows || 4,
'aria-label': props['aria-label'] || props.name,
}))`
width: 100%;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
resize: vertical;
min-height: 100px;
&:focus {
border-color: #667eea;
outline: none;
}
`;
const FormSelect = styled.select.attrs(props => ({
'aria-label': props['aria-label'] || props.name,
}))`
width: 100%;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
background: white;
cursor: pointer;
&:focus {
border-color: #667eea;
outline: none;
}
`;
7.2 可访问性组件 #
jsx
const AccessibleButton = styled.button.attrs(props => ({
role: 'button',
'aria-label': props['aria-label'] || props.title,
'aria-disabled': props.disabled,
tabIndex: props.disabled ? -1 : 0,
}))`
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
opacity: ${props => props.disabled ? 0.6 : 1};
&:focus-visible {
outline: 2px solid #667eea;
outline-offset: 2px;
}
&:hover:not(:disabled) {
background: #5a6fd6;
}
`;
const IconButton = styled.button.attrs(props => ({
'aria-label': props.label,
title: props.label,
}))`
width: 40px;
height: 40px;
border-radius: 50%;
background: #f0f0f0;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background: #e0e0e0;
}
&:focus-visible {
outline: 2px solid #667eea;
outline-offset: 2px;
}
`;
7.3 图像组件 #
jsx
const Image = styled.img.attrs(props => ({
loading: props.loading || 'lazy',
decoding: 'async',
alt: props.alt || '',
}))`
max-width: 100%;
height: auto;
border-radius: ${props => props.rounded ? '50%' : '8px'};
object-fit: ${props => props.cover ? 'cover' : 'contain'};
`;
const Avatar = styled.img.attrs(props => ({
loading: 'lazy',
alt: props.alt || 'User avatar',
}))`
width: ${props => props.size || '48px'};
height: ${props => props.size || '48px'};
border-radius: 50%;
object-fit: cover;
border: 2px solid #e0e0e0;
`;
八、attrs vs Props #
8.1 区别对比 #
jsx
const InputWithAttrs = styled.input.attrs({
type: 'text',
})`
padding: 12px;
`;
const InputWithProps = styled.input`
padding: 12px;
`;
function App() {
return (
<>
<InputWithAttrs />
<InputWithProps type="text" />
</>
);
}
8.2 选择建议 #
text
使用 attrs 的场景
├── 固定的 HTML 属性(type, disabled, readOnly)
├── 默认属性值
├── 可访问性属性(aria-*, role)
├── 表单属性(name, form, autoComplete)
└── 数据属性(data-*)
使用 Props 的场景
├── 动态样式值
├── 条件样式
├── 组件状态
└── 主题相关
九、性能考虑 #
9.1 避免内联函数 #
jsx
const Input = styled.input.attrs(props => ({
type: props.type || 'text',
placeholder: props.placeholder || 'Enter value',
}))`
padding: 12px;
`;
const getInputAttrs = (props) => ({
type: props.type || 'text',
placeholder: props.placeholder || 'Enter value',
});
const OptimizedInput = styled.input.attrs(getInputAttrs)`
padding: 12px;
`;
9.2 静态属性优化 #
jsx
const staticAttrs = {
type: 'text',
autoComplete: 'off',
spellCheck: 'false',
};
const Input = styled.input.attrs(staticAttrs)`
padding: 12px;
border: 2px solid #e0e0e0;
`;
十、总结 #
attrs 使用技巧速查表:
| 用法 | 示例 |
|---|---|
| 静态属性 | .attrs({ type: 'text' }) |
| 动态属性 | .attrs(p => ({ disabled: p.loading })) |
| 链式调用 | .attrs({}).attrs(p => ({})) |
| 属性合并 | 继承时自动合并 |
| 类型安全 | .attrs<Props>(p => ({})) |
下一步:学习 动画支持 掌握 CSS 动画和关键帧的使用。
最后更新:2026-03-28