You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

接入Redux/Redux Toolkit后Gatsby项目Material-UI抽屉和顶栏过渡动画失效

问题根本原因

你把styled自定义组件、样式mixin的定义全部写在了组件函数内部,每次Redux状态变更触发组件重渲染时,都会生成全新的styled组件实例,React会直接销毁旧DOM节点挂载新节点,不会触发过渡动画,相当于直接切换状态。


修复步骤

1. drawer.js 修改

将所有styled定义、样式mixin移到组件函数外部,依赖的drawerWidth通过props传递:

import React from 'react';
import {Divider, IconButton, List, styled, useTheme} from "@mui/material";
import MuiDrawer from '@mui/material/Drawer';
import {ChevronLeft, ChevronRight} from "@mui/icons-material";
import {useDispatch, useSelector} from "react-redux";
import {layoutSelector} from "../../store/layoutStore";
import {toggleDrawerOpen} from "../../store/layoutStore/index";

// 全部移到组件外部定义
const DrawerHeader = styled('div')(({ theme }) => ({
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    padding: theme.spacing(0, 1),
    ...theme.mixins.toolbar,
}));

const openedMixin = (theme, drawerWidth) => ({
    width: drawerWidth,
    backgroundColor: theme.palette.primary.main,
    transition: theme.transitions.create('width', {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.enteringScreen,
    }),
    overflowX: 'hidden',
});

const closedMixin = (theme) => ({
    backgroundColor: theme.palette.primary.main,
    transition: theme.transitions.create('width', {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
    }),
    overflowX: 'hidden',
    width: `calc(${theme.spacing(7)} + 1px)`,
    [theme.breakpoints.up('sm')]: {
        width: `calc(${theme.spacing(9)} + 1px)`,
    },
});

const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' && prop !== 'drawerWidth' })(
    ({ theme, open, drawerWidth }) => ({
        width: drawerWidth,
        flexShrink: 0,
        whiteSpace: 'nowrap',
        boxSizing: 'border-box',
        color: "#fff",
        ...(open && {
            ...openedMixin(theme, drawerWidth),
            '& .MuiDrawer-paper': openedMixin(theme, drawerWidth),
        }),
        ...(!open && {
            ...closedMixin(theme),
            '& .MuiDrawer-paper': closedMixin(theme),
        }),
    }),
);

const Index = () => {
    const {drawerWidth, drawerOpen} = useSelector(layoutSelector);
    const dispatch = useDispatch();
    const theme = useTheme();
    
    return (
        <Drawer variant="permanent" open={drawerOpen} drawerWidth={drawerWidth}>
            <DrawerHeader>
                <IconButton
                    onClick={() => dispatch(toggleDrawerOpen())}
                >
                    {theme.direction === 'rtl' ? <ChevronRight  color={'white'}/> : <ChevronLeft color={'white'} />}
                </IconButton>
            </DrawerHeader>
            <Divider />
            <List>
                {/* 原有列表内容 */}
            </List>
            <Divider />
        </Drawer>
    )
}

export default Index;

2. topbar.js 同逻辑修改

把自定义AppBar的styled定义移到组件外部:

import React from 'react';
import {IconButton, styled, Toolbar} from "@mui/material";
import MuiAppBar from '@mui/material/AppBar';
import {Menu} from "@mui/icons-material";
import {useDispatch, useSelector} from "react-redux";
import {layoutSelector, toggleDrawerOpen} from "../../store/layoutStore";

// 移到组件外部定义
const AppBar = styled(MuiAppBar, {
    shouldForwardProp: (prop) => prop !== 'open' && prop !== 'drawerWidth',
})(({theme, open, drawerWidth}) => ({
    color: theme.palette.primary,
    zIndex: theme.zIndex.drawer + 1,
    transition: theme.transitions.create(['width', 'margin'], {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
    }),
    ...(open && {
        marginLeft: drawerWidth,
        width: `calc(100% - ${drawerWidth}px)`,
        transition: theme.transitions.create(['width', 'margin'], {
            easing: theme.transitions.easing.sharp,
            duration: theme.transitions.duration.enteringScreen,
        }),
    }),
}));

const Index = () => {
    const {drawerWidth, drawerOpen} = useSelector(layoutSelector);
    const dispatch = useDispatch();
    return (
        <AppBar position="fixed" open={drawerOpen} drawerWidth={drawerWidth}>
            <Toolbar color={'white'} style={{background: 'white'}}>
                <IconButton
                    color="secondary"
                    aria-label="open drawer"
                    onClick={() => dispatch(toggleDrawerOpen())}
                    edge="start"
                >
                    <Menu/>
                </IconButton>
            </Toolbar>
        </AppBar>
    )
}

export default Index;

可选优化

如果layoutSelector没有使用createSelector缓存返回值,建议封装成缓存selector,避免不必要的重渲染:

// layoutStore/index.js
import { createSelector } from '@reduxjs/toolkit';

const selectLayout = state => state.layout;
export const layoutSelector = createSelector(
  [selectLayout],
  (layout) => ({
    drawerWidth: layout.drawerWidth,
    drawerOpen: layout.drawerOpen
  })
);

内容的提问来源于stack exchange,提问作者Abid.Saifeddine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 19:27:04