TypeScript中为通用Styled Components传递悬停/点击状态样式Props
用Styled Components实现带状态样式的通用Card组件
核心问题解决思路
- Hover状态样式:直接在Styled Components中使用
&:hover伪类定义,无需额外传递特殊Props,也支持通过自定义Props注入个性化hover样式。 - 点击激活持久状态:通过组件状态(或父组件状态)维护激活标识,给Styled组件传递
isActiveProps,根据该标识切换样式,实现点击后保持状态直到再次切换。
完整代码实现
第一步:创建StyledCard基础组件
import styled from 'styled-components'; interface StyledCardProps { // 自定义基础样式 baseStyle?: React.CSSProperties; // 自定义hover样式 hoverStyle?: React.CSSProperties; // 自定义激活样式 activeStyle?: React.CSSProperties; // 是否处于激活状态 isActive?: boolean; } const StyledCard = styled.div<StyledCardProps>` // 基础默认样式 padding: 16px; border-radius: 8px; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.3s ease; box-sizing: border-box; width: 100%; max-width: 320px; // 合并传入的基础样式 ${props => props.baseStyle && Object.entries(props.baseStyle).map(([key, value]) => `${key}: ${value};`).join('')} // Hover状态样式 &:hover { background-color: #f5f5f5; // 合并自定义hover样式 ${props => props.hoverStyle && Object.entries(props.hoverStyle).map(([key, value]) => `${key}: ${value};`).join('')} } // 激活状态样式 ${props => props.isActive && ` background-color: #e3f2fd; border-color: #2196f3; // 合并自定义激活样式 ${props.activeStyle && Object.entries(props.activeStyle).map(([key, value]) => `${key}: ${value};`).join('')} `} `;
第二步:封装通用Card组件
import { useState } from 'react'; interface CardProps { title: string; avatarUrl: string; name: string; badge?: string; baseStyle?: React.CSSProperties; hoverStyle?: React.CSSProperties; activeStyle?: React.CSSProperties; id: string; } const Card = ({ title, avatarUrl, name, badge, baseStyle, hoverStyle, activeStyle, id }: CardProps) => { // 单Card场景:维护自身激活状态;多Card切换请将activeId提升至父组件 const [activeId, setActiveId] = useState<string | null>(null); const handleCardClick = () => { // 点击切换激活状态:已激活则取消,未激活则设置为当前id setActiveId(prev => prev === id ? null : id); }; return ( <StyledCard baseStyle={baseStyle} hoverStyle={hoverStyle} activeStyle={activeStyle} isActive={activeId === id} onClick={handleCardClick} > <div style={{ display: 'flex', gap: '12px', alignItems: 'center', marginBottom: '12px' }}> <img src={avatarUrl} alt={`${name}的头像`} style={{ width: 48, height: 48, borderRadius: '50%', objectFit: 'cover' }} /> <div> <h3 style={{ margin: 0, fontSize: '18px' }}>{title}</h3> <p style={{ margin: '4px 0 0', color: '#666' }}>{name}</p> </div> </div> {badge && ( <span style={{ padding: '2px 8px', backgroundColor: '#ffeb3b', borderRadius: '4px', fontSize: '12px', fontWeight: 500 }}> {badge} </span> )} </StyledCard> ); };
第三步:使用示例
// 单Card使用 <Card id="card-1" title="前端工程师" name="张三" avatarUrl="https://via.placeholder.com/48" badge="在职" baseStyle={{ backgroundColor: '#fff' }} hoverStyle={{ backgroundColor: '#f0f8ff' }} activeStyle={{ backgroundColor: '#e6f7ff', borderColor: '#1890ff' }} /> // 多Card切换(需将activeId提升至父组件) const CardGroup = () => { const [activeId, setActiveId] = useState<string | null>(null); const cardList = [ { id: 'card-1', title: '前端工程师', name: '张三', avatarUrl: 'https://via.placeholder.com/48', badge: '在职' }, { id: 'card-2', title: '后端工程师', name: '李四', avatarUrl: 'https://via.placeholder.com/48', badge: '离职' }, ]; return ( <div style={{ display: 'flex', gap: '16px', padding: '20px' }}> {cardList.map(card => ( <Card key={card.id} {...card} baseStyle={{ backgroundColor: '#fff' }} isActive={activeId === card.id} onClick={() => setActiveId(prev => prev === card.id ? null : card.id)} /> ))} </div> ); };
常见问题排查
- Hover样式不生效:检查是否漏写
&(必须用&:hover而非:hover,否则伪类会作用于子元素);检查基础样式的background-color是否覆盖了hover样式;确保添加了transition让变化可见。 - 激活状态不保持:确认
isActiveProp正确关联了状态值;多Card场景下需将激活状态提升至父组件,避免每个Card维护独立状态导致冲突。
内容的提问来源于stack exchange,提问作者Jack Repear
相关产品推荐
相关产品推荐

