你的项目大量使用了以下静态方法:
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 nullimport { message, notification, Modal } from 'antd';
// 直接调用静态方法
const handleClick = () => {
message.success('操作成功');
notification.info({
message: '通知标题',
description: '这是通知内容',
});
Modal.confirm({
title: '确认删除?',
onOk: () => { /* ... */ }
});
};
问题: 在 v6 中这些方法需要 Context 才能正常工作!
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 <Button onClick={handleClick}>点击</Button>;
};
export default MyComponent;
前提: 必须在根组件包裹 <App> 组件:
// src/index.tsx 或 App.tsx
import { App } from 'antd';
const Root = () => {
return (
<App>
<YourApp />
</App>
);
};
对于 message 和 notification,antd 还提供了独立的 hook:
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}
<Button onClick={handleClick}>点击</Button>
</>
);
};
缺点: 每个组件都要声明,比较繁琐。
如果你的项目中有太多地方使用静态方法,一个一个改太麻烦,可以创建全局单例:
// 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 中初始化:
// 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 (
<ConfigProvider>
<AntdApp>
<InitInstances />
{children}
</AntdApp>
</ConfigProvider>
);
};
export default AppProvider;
在业务代码中使用:
// 任意组件中
import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
const handleClick = () => {
globalMessage.success('操作成功');
globalNotification.info({
message: '通知标题',
description: '内容',
});
globalModal.confirm({
title: '确认?',
onOk: () => {},
});
};
优点:
缺点:
根据你的项目规模(581 个文件),我强烈推荐使用方案 3(全局单例),原因:
App.useApp()# 在你的新项目中创建
touch src/utils/antd-global.ts
复制上面 方案 3 的代码到这个文件。
修改你的 src/theme/AppProvider.tsx(已经由工具生成),添加实例初始化。
使用我提供的工具批量替换:
# 预览替换
node migration-tools/replace-static-import.js ./src --dry-run
# 执行替换
node migration-tools/replace-static-import.js ./src --backup
替换规则:
// 旧的 import
import { message } from 'antd';
// 新的 import
import { globalMessage as message } from '@/utils/antd-global';
这样你的业务代码几乎不需要修改!
| 方案 | 适用场景 | 改动量 | 推荐度 |
|---|---|---|---|
| 方案 1: App.useApp() | 新项目、小项目 | 大 | ⭐⭐⭐⭐⭐ (官方推荐) |
| 方案 2: 独立 Hook | 少量使用 | 大 | ⭐⭐⭐ |
| 方案 3: 全局单例 | 大项目、使用广泛 | 小 | ⭐⭐⭐⭐ (过渡方案) |
你的情况:
src/utils/antd-global.tsAppProvider.tsx 初始化实例<App>原因: 实例未初始化或 <App> 组件未包裹。
解决:
// 检查 src/index.tsx
import { App } from 'antd';
ReactDOM.render(
<App> {/* 必须包裹 */}
<YourApp />
</App>,
document.getElementById('root')
);
原因: 工具函数不在 React 组件中,无法使用 hook。
解决: 使用方案 3 的全局单例:
// 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('请求失败');
}
};
解决: 同样使用全局单例:
// 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('登录失败');
}
}
原因: 异步回调中 Context 可能失效。
解决: 使用全局单例方案不受影响:
const handleClick = () => {
setTimeout(() => {
globalMessage.success('延迟消息'); // ✅ 正常工作
}, 1000);
};
我已经为你准备了一个专门的替换脚本 replace-static-import.js,它会自动:
message, notification, Modal 的文件使用方法:
# 预览
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 小时!
App.useApp()App.useApp()立即执行:
antd-global.ts 文件稍后执行:
长期计划:
App.useApp()记住:迁移不是一蹴而就的,先让代码跑起来,再逐步优化! 🚀