接入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
相关产品推荐
相关产品推荐

