# 🚨 静态方法迁移专项指南 ## ⚠️ 重要警告 你的项目大量使用了以下静态方法: - `message.success()`, `message.error()`, `message.warning()`, `message.info()`, `message.loading()` - `notification.success()`, `notification.error()`, `notification.warning()`, `notification.info()`, `notification.open()` - `Modal.confirm()`, `Modal.info()`, `Modal.success()`, `Modal.error()`, `Modal.warning()` **这是 v4 → v6 迁移中最重要的变更之一!** 在 v6 中,这些静态方法**必须**在特定的上下文中使用,否则会出现以下问题: - ❌ 调用后没有任何反应 - ❌ 控制台警告: `instance is null` - ❌ 主题样式不生效 - ❌ 国际化不生效 --- ## 📊 迁移方案对比 ### ❌ v4 方式(不推荐) ```typescript import { message, notification, Modal } from 'antd'; // 直接调用静态方法 const handleClick = () => { message.success('操作成功'); notification.info({ message: '通知标题', description: '这是通知内容', }); Modal.confirm({ title: '确认删除?', onOk: () => { /* ... */ } }); }; ``` **问题:** 在 v6 中这些方法需要 Context 才能正常工作! --- ### ✅ v6 方式 - 方案 1: 使用 App.useApp() Hook(推荐) ```typescript import { App, Button } from 'antd'; const MyComponent = () => { const { message, notification, modal } = App.useApp(); const handleClick = () => { message.success('操作成功'); notification.info({ message: '通知标题', description: '这是通知内容', }); modal.confirm({ title: '确认删除?', onOk: () => { /* ... */ } }); }; return ; }; export default MyComponent; ``` **前提:** 必须在根组件包裹 `` 组件: ```typescript // src/index.tsx 或 App.tsx import { App } from 'antd'; const Root = () => { return ( ); }; ``` --- ### ✅ v6 方式 - 方案 2: 使用独立 Hook(推荐) 对于 message 和 notification,antd 还提供了独立的 hook: ```typescript import { message, notification } from 'antd'; const MyComponent = () => { const [messageApi, messageContextHolder] = message.useMessage(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const handleClick = () => { messageApi.success('操作成功'); notificationApi.info({ message: '通知标题', description: '这是通知内容', }); }; return ( <> {messageContextHolder} {notificationContextHolder} ); }; ``` **缺点:** 每个组件都要声明,比较繁琐。 --- ### ✅ v6 方式 - 方案 3: 全局单例(兼容性方案) 如果你的项目中有太多地方使用静态方法,一个一个改太麻烦,可以创建全局单例: ```typescript // src/utils/antd-global.ts import { message, notification, Modal } from 'antd'; import type { MessageInstance } from 'antd/es/message/interface'; import type { NotificationInstance } from 'antd/es/notification/interface'; import type { HookAPI } from 'antd/es/modal/useModal'; let messageInstance: MessageInstance; let notificationInstance: NotificationInstance; let modalInstance: HookAPI; // 初始化实例 export const setMessageInstance = (instance: MessageInstance) => { messageInstance = instance; }; export const setNotificationInstance = (instance: NotificationInstance) => { notificationInstance = instance; }; export const setModalInstance = (instance: HookAPI) => { modalInstance = instance; }; // 导出全局 message export const globalMessage = { success: (content: string, duration?: number) => { if (!messageInstance) { console.error('Message instance not initialized'); return; } return messageInstance.success(content, duration); }, error: (content: string, duration?: number) => { if (!messageInstance) { console.error('Message instance not initialized'); return; } return messageInstance.error(content, duration); }, warning: (content: string, duration?: number) => { if (!messageInstance) { console.error('Message instance not initialized'); return; } return messageInstance.warning(content, duration); }, info: (content: string, duration?: number) => { if (!messageInstance) { console.error('Message instance not initialized'); return; } return messageInstance.info(content, duration); }, loading: (content: string, duration?: number) => { if (!messageInstance) { console.error('Message instance not initialized'); return; } return messageInstance.loading(content, duration); }, }; // 导出全局 notification export const globalNotification = { success: (config: any) => { if (!notificationInstance) { console.error('Notification instance not initialized'); return; } return notificationInstance.success(config); }, error: (config: any) => { if (!notificationInstance) { console.error('Notification instance not initialized'); return; } return notificationInstance.error(config); }, warning: (config: any) => { if (!notificationInstance) { console.error('Notification instance not initialized'); return; } return notificationInstance.warning(config); }, info: (config: any) => { if (!notificationInstance) { console.error('Notification instance not initialized'); return; } return notificationInstance.info(config); }, open: (config: any) => { if (!notificationInstance) { console.error('Notification instance not initialized'); return; } return notificationInstance.open(config); }, }; // 导出全局 modal export const globalModal = { confirm: (config: any) => { if (!modalInstance) { console.error('Modal instance not initialized'); return; } return modalInstance.confirm(config); }, info: (config: any) => { if (!modalInstance) { console.error('Modal instance not initialized'); return; } return modalInstance.info(config); }, success: (config: any) => { if (!modalInstance) { console.error('Modal instance not initialized'); return; } return modalInstance.success(config); }, error: (config: any) => { if (!modalInstance) { console.error('Modal instance not initialized'); return; } return modalInstance.error(config); }, warning: (config: any) => { if (!modalInstance) { console.error('Modal instance not initialized'); return; } return modalInstance.warning(config); }, }; ``` 在 AppProvider 中初始化: ```typescript // src/AppProvider.tsx import React from 'react'; import { ConfigProvider, App as AntdApp, theme } from 'antd'; import { setMessageInstance, setNotificationInstance, setModalInstance } from './utils/antd-global'; const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const InitInstances = () => { const { message, notification, modal } = AntdApp.useApp(); React.useEffect(() => { setMessageInstance(message); setNotificationInstance(notification); setModalInstance(modal); }, [message, notification, modal]); return null; }; return ( {children} ); }; export default AppProvider; ``` 在业务代码中使用: ```typescript // 任意组件中 import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global'; const handleClick = () => { globalMessage.success('操作成功'); globalNotification.info({ message: '通知标题', description: '内容', }); globalModal.confirm({ title: '确认?', onOk: () => {}, }); }; ``` **优点:** - ✅ 最小改动,只需替换 import - ✅ 适合代码量大、使用广泛的场景 - ✅ 保持原有调用方式 **缺点:** - ⚠️ 不是官方推荐的最佳实践 - ⚠️ 需要额外维护全局单例代码 --- ## 🎯 推荐的迁移策略 根据你的项目规模(581 个文件),我**强烈推荐使用方案 3(全局单例)**,原因: 1. **改动最小** - 只需替换 import 语句 2. **风险最低** - 不需要修改每个组件的逻辑 3. **可逐步优化** - 迁移完成后,可以逐步改为 `App.useApp()` ### 迁移步骤 #### Step 1: 创建全局单例工具 ```bash # 在你的新项目中创建 touch src/utils/antd-global.ts ``` 复制上面 **方案 3** 的代码到这个文件。 #### Step 2: 更新 AppProvider 修改你的 `src/theme/AppProvider.tsx`(已经由工具生成),添加实例初始化。 #### Step 3: 批量替换 import 语句 使用我提供的工具批量替换: ```bash # 预览替换 node migration-tools/replace-static-import.js ./src --dry-run # 执行替换 node migration-tools/replace-static-import.js ./src --backup ``` 替换规则: ```typescript // 旧的 import import { message } from 'antd'; // 新的 import import { globalMessage as message } from '@/utils/antd-global'; ``` 这样你的业务代码**几乎不需要修改**! --- ## 📝 各方案适用场景 | 方案 | 适用场景 | 改动量 | 推荐度 | |------|---------|--------|--------| | 方案 1: App.useApp() | 新项目、小项目 | 大 | ⭐⭐⭐⭐⭐ (官方推荐) | | 方案 2: 独立 Hook | 少量使用 | 大 | ⭐⭐⭐ | | 方案 3: 全局单例 | 大项目、使用广泛 | 小 | ⭐⭐⭐⭐ (过渡方案) | **你的情况:** - ✅ 项目规模大(581 个文件) - ✅ 大量使用静态方法 - ✅ 建议使用**方案 3**先快速迁移 - ✅ 后续逐步优化为方案 1 --- ## 🔧 迁移检查清单 ### Phase 1: 准备工作 - [ ] 创建 `src/utils/antd-global.ts` - [ ] 更新 `AppProvider.tsx` 初始化实例 - [ ] 确保根组件包裹了 `` ### Phase 2: 批量替换 - [ ] 备份代码(使用 Git) - [ ] 运行替换脚本(--dry-run) - [ ] 检查预览结果 - [ ] 执行实际替换(--backup) ### Phase 3: 测试验证 - [ ] 测试 message.success/error/warning/info/loading - [ ] 测试 notification.success/error/warning/info/open - [ ] 测试 Modal.confirm/info/success/error/warning - [ ] 检查主题样式是否正确 - [ ] 检查国际化是否正常 ### Phase 4: 特殊情况处理 - [ ] 检查异步场景(setTimeout 等) - [ ] 检查工具类函数中的调用 - [ ] 检查 Redux action/saga 中的调用 - [ ] 检查 request 拦截器中的调用 --- ## ⚠️ 常见问题和解决方案 ### 问题 1: 调用静态方法没有反应 **原因:** 实例未初始化或 `` 组件未包裹。 **解决:** ```typescript // 检查 src/index.tsx import { App } from 'antd'; ReactDOM.render( {/* 必须包裹 */} , document.getElementById('root') ); ``` ### 问题 2: 在工具函数中使用静态方法报错 **原因:** 工具函数不在 React 组件中,无法使用 hook。 **解决:** 使用方案 3 的全局单例: ```typescript // src/utils/api.ts import { globalMessage } from './antd-global'; export const request = async (url: string) => { try { const response = await fetch(url); globalMessage.success('请求成功'); return response; } catch (error) { globalMessage.error('请求失败'); } }; ``` ### 问题 3: 在 Redux Saga 中使用静态方法 **解决:** 同样使用全局单例: ```typescript // src/store/sagas/user.saga.ts import { globalMessage } from '@/utils/antd-global'; import { call, put } from 'redux-saga/effects'; function* loginSaga(action: any) { try { const result = yield call(loginApi, action.payload); globalMessage.success('登录成功'); yield put({ type: 'LOGIN_SUCCESS', payload: result }); } catch (error) { globalMessage.error('登录失败'); } } ``` ### 问题 4: 在 setTimeout 中使用静态方法失效 **原因:** 异步回调中 Context 可能失效。 **解决:** 使用全局单例方案不受影响: ```typescript const handleClick = () => { setTimeout(() => { globalMessage.success('延迟消息'); // ✅ 正常工作 }, 1000); }; ``` --- ## 🚀 快速迁移脚本 我已经为你准备了一个专门的替换脚本 `replace-static-import.js`,它会自动: 1. 检测所有使用 `message`, `notification`, `Modal` 的文件 2. 替换 import 语句为全局单例 3. 保持业务代码不变 使用方法: ```bash # 预览 node migration-tools/replace-static-import.js ./src --dry-run # 执行 node migration-tools/replace-static-import.js ./src --backup ``` --- ## 📊 迁移工作量评估 基于你的项目: | 操作 | 预计耗时 | |------|---------| | 创建全局单例工具 | 30 分钟 | | 更新 AppProvider | 15 分钟 | | 批量替换 import | 1 小时 | | 测试验证 | 2-3 小时 | | 处理特殊情况 | 1-2 小时 | | **总计** | **5-7 小时** | 如果手动一个一个改为 `App.useApp()`,预计需要 **20-30 小时**! --- ## 💡 最佳实践建议 ### 短期(迁移阶段) 1. ✅ 使用全局单例快速迁移 2. ✅ 确保所有功能正常 3. ✅ 重点测试异步场景 ### 中期(优化阶段) 1. ✅ 对于新开发的组件,使用 `App.useApp()` 2. ✅ 逐步重构核心组件为标准方式 3. ✅ 保持两种方式共存 ### 长期(重构阶段) 1. ✅ 所有组件改用 `App.useApp()` 2. ✅ 移除全局单例代码 3. ✅ 完全符合 v6 最佳实践 --- ## 🎯 下一步行动 1. **立即执行:** - [ ] 创建 `antd-global.ts` 文件 - [ ] 运行我提供的替换脚本 - [ ] 测试核心功能 2. **稍后执行:** - [ ] 处理特殊场景(工具函数、Saga 等) - [ ] 添加单元测试 - [ ] 更新团队文档 3. **长期计划:** - [ ] 新组件使用 `App.useApp()` - [ ] 逐步重构旧组件 - [ ] 最终移除全局单例 --- **记住:迁移不是一蹴而就的,先让代码跑起来,再逐步优化!** 🚀