feat: 创建 PageTransitionWrapper - 封装页面过渡动画
## 功能
创建页面过渡动画包装组件,封装复杂的嵌套逻辑
## 核心特性
### 1. 页面过渡动画
使用 framer-motion 提供流畅的页面切换动画:
- **AnimatePresence**: 管理组件进入/退出动画
- **MotionBox**: 动画化的 Box 组件
- mode="wait": 等待退出动画完成后再进入
### 2. 错误边界隔离
**ErrorBoundary 包裹**
- 隔离页面错误,确保导航栏不受影响
- 错误发生时,导航栏仍然可用
- 提供降级 UI(由 ErrorBoundary 组件处理)
### 3. 懒加载支持
**Suspense 边界**
- 支持 React.lazy() 懒加载路由组件
- 显示 PageLoader 组件作为 fallback
- 可自定义加载消息
### 4. 配置化设计
支持自定义配置:
- `animationConfig`: 自定义动画参数
- initial: 初始状态
- animate: 动画状态
- exit: 退出状态
- transition: 过渡配置
- `loaderMessage`: 自定义加载消息
### 5. React.memo 优化
使用 memo 避免不必要的重新渲染
## 封装的复杂逻辑
原 MainLayout 中 18 行复杂嵌套:
```
<Box flex="1" position="relative" overflow="hidden">
<AnimatePresence mode="wait">
<MotionBox key={location.pathname} ...>
<ErrorBoundary>
<Suspense fallback={<PageLoader />}>
<Outlet />
</Suspense>
</ErrorBoundary>
</MotionBox>
</AnimatePresence>
</Box>
```
现在简化为:
```
<PageTransitionWrapper location={location} animationConfig={...}>
<Outlet />
</PageTransitionWrapper>
```
## 优势
- ✅ 单一职责:只负责页面过渡和错误隔离
- ✅ 配置化:支持自定义动画
- ✅ 可复用:可在其他 Layout 中使用
- ✅ 可测试:独立组件,易于单元测试
- ✅ 可维护:清晰的组件边界
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
66
src/layouts/components/PageTransitionWrapper.js
Normal file
66
src/layouts/components/PageTransitionWrapper.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// src/layouts/components/PageTransitionWrapper.js
|
||||||
|
import React, { Suspense, memo } from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import ErrorBoundary from '../../components/ErrorBoundary';
|
||||||
|
import PageLoader from '../../components/Loading/PageLoader';
|
||||||
|
|
||||||
|
// 创建 motion 包裹的 Box 组件
|
||||||
|
const MotionBox = motion(Box);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面过渡动画包裹组件
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - 页面切换时的过渡动画(AnimatePresence)
|
||||||
|
* - 错误边界隔离(ErrorBoundary)
|
||||||
|
* - 懒加载支持(Suspense)
|
||||||
|
*
|
||||||
|
* 优化:
|
||||||
|
* - ✅ 使用 memo 避免不必要的重新渲染
|
||||||
|
* - ✅ 支持自定义动画配置
|
||||||
|
* - ✅ 错误隔离,确保导航栏不受影响
|
||||||
|
*
|
||||||
|
* @param {React.ReactNode} children - 要渲染的子组件(通常是 <Outlet />)
|
||||||
|
* @param {object} location - 路由位置对象(用于动画 key)
|
||||||
|
* @param {object} animationConfig - 自定义动画配置
|
||||||
|
* @param {string} loaderMessage - 加载时显示的消息
|
||||||
|
*/
|
||||||
|
const PageTransitionWrapper = memo(({
|
||||||
|
children,
|
||||||
|
location,
|
||||||
|
animationConfig = {
|
||||||
|
initial: { opacity: 0, y: 20 },
|
||||||
|
animate: { opacity: 1, y: 0 },
|
||||||
|
exit: { opacity: 0, y: -20 },
|
||||||
|
transition: { duration: 0.2 }
|
||||||
|
},
|
||||||
|
loaderMessage = '页面加载中...'
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box flex="1" position="relative" overflow="hidden">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<MotionBox
|
||||||
|
key={location.pathname}
|
||||||
|
initial={animationConfig.initial}
|
||||||
|
animate={animationConfig.animate}
|
||||||
|
exit={animationConfig.exit}
|
||||||
|
transition={animationConfig.transition}
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
>
|
||||||
|
{/* 错误边界:隔离页面错误,确保导航栏仍可用 */}
|
||||||
|
<ErrorBoundary>
|
||||||
|
{/* Suspense:支持 React.lazy() 懒加载 */}
|
||||||
|
<Suspense fallback={<PageLoader message={loaderMessage} />}>
|
||||||
|
{children}
|
||||||
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</MotionBox>
|
||||||
|
</AnimatePresence>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
PageTransitionWrapper.displayName = 'PageTransitionWrapper';
|
||||||
|
|
||||||
|
export default PageTransitionWrapper;
|
||||||
Reference in New Issue
Block a user