Styled Components过渡动画不生效 重渲染致按钮无动画问题
React重渲染导致按钮transition动画失效解决方案
问题复现
在React+styled-components技术栈下给按钮添加状态切换过渡效果时,点击按钮后动画完全不触发,相关代码片段如下:
按钮调用代码:
<S.Button onClick={() => setActive(!isActive)} isActive={isActive}> {/* {isActive ? 'Daily stats' : 'All time stats'} */} asd </S.Button>
按钮样式定义:
export const Button = styled.button<{ isActive: boolean }>` font-family: ${({ theme }) => theme.font.family.book}; background-color: ${({ isActive, theme }) => isActive ? '#cae8ee' : theme.color.white}; border: 1px solid ${({ isActive, theme }) => (isActive ? theme.color.primary : '#f0f0f0')}; transition: all 0.3s ease-in-out; &::before { content: ''; background-color: ${({ isActive, theme }) => isActive ? theme.color.primary : '#f0f0f0'}; left: ${({ isActive }) => (isActive ? '150px' : '2px')}; transition: all 0.3s ease-in-out; } `;
排查确认问题根因:按钮点击切换isActive状态后,绑定的useEffect会立即触发接口请求,父组件因请求状态(loading、返回数据等)更新发生重渲染,连带按钮组件重渲染,直接打断了CSS过渡的执行流程。
相关副作用代码:
useEffect(() => { if (statsButtonActive) { fetchDailyData(); } else { fetchAllTimeData(); } // eslint-disable-next-line }, [statsButtonActive]);
可行修复方案
1. 组件拆分+React.memo隔离重渲染(推荐)
把按钮抽成独立组件,用React.memo做渲染缓存,只有按钮自身依赖的props变化时才重渲染,父组件拉取数据产生的状态更新不会影响按钮。
// 单独抽离按钮组件 import { memo } from 'react'; const StatsToggleButton = memo(function({ isActive, onToggle }: { isActive: boolean; onToggle: () => void; }) { return ( <S.Button onClick={onToggle} isActive={isActive}> {isActive ? 'Daily stats' : 'All time stats'} </S.Button> ) }); // 父组件中使用 <StatsToggleButton isActive={statsButtonActive} onToggle={() => setStatsButtonActive(prev => !prev)} />
注意:传入的onToggle要保证引用稳定,直接传内联的prev状态更新函数即可,React本身对setState的返回函数做了引用稳定处理,不会触发memo失效;如果有额外自定义逻辑,用useCallback包裹处理。
2. 延迟触发数据请求,错开动画执行周期
如果不想拆分组件,直接调整点击逻辑,等动画执行完成后再触发接口请求,避免重渲染和动画周期冲突:
const handleToggleStats = () => { const nextState = !statsButtonActive; setStatsButtonActive(nextState); // 动画时长0.3s,延迟350ms触发请求,留少量缓冲时间 setTimeout(() => { nextState ? fetchDailyData() : fetchAllTimeData(); }, 350); }; // 按钮绑定这个点击事件即可 <S.Button onClick={handleToggleStats} isActive={statsButtonActive}> {statsButtonActive ? 'Daily stats' : 'All time stats'} </S.Button>
这个方案改动量最小,但后续如果调整动画时长,需要同步修改延迟时间,维护成本稍高。
3. 补全伪元素必要样式
当前::before伪元素缺少定位属性,默认静态定位下left配置不生效,哪怕没有重渲染问题,伪元素的滑动动画也无法正常播放,需要补全对应样式:
&::before { content: ''; position: absolute; // 按钮本身要加 position: relative top: 2px; width: 20px; // 按实际设计稿设置尺寸 height: 20px; border-radius: 50%; // 滑块类按钮按需配置 background-color: ${({ isActive, theme }) => isActive ? theme.color.primary : '#f0f0f0'}; left: ${({ isActive }) => (isActive ? '150px' : '2px')}; transition: all 0.3s ease-in-out; }
内容的提问来源于stack exchange,提问作者kakakakakakakk
相关产品推荐
相关产品推荐

