372 lines
8.3 KiB
Markdown
372 lines
8.3 KiB
Markdown
# Ant Design 复制和 Message 功能使用指南
|
||
|
||
> **作者:阿瑞**
|
||
> **版本:1.0.0**
|
||
> **适用范围:Ant Design 5.x + Next.js 15 + React 19**
|
||
|
||
## 📖 概述
|
||
|
||
本文档详细介绍如何在 Ant Design 5.x 中正确使用复制功能和 message 组件,避免常见的警告和错误,提供最佳实践方案。
|
||
|
||
## 🚨 常见警告及解决方案
|
||
|
||
### 1. Message 静态方法警告
|
||
|
||
**警告信息:**
|
||
```
|
||
Warning: [antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.
|
||
```
|
||
|
||
**产生原因:**
|
||
- Ant Design 5.x 中的静态方法(如 `message.success()`)无法获取动态主题上下文
|
||
- 静态方法不在 React 组件树中,无法访问 `ConfigProvider` 和 `App` 组件提供的上下文
|
||
|
||
## 💡 正确使用方法
|
||
|
||
### 1. App 组件配置
|
||
|
||
首先确保在 `_app.tsx` 中正确配置 `App` 组件:
|
||
|
||
```tsx
|
||
// src/pages/_app.tsx
|
||
import { ConfigProvider, App } from "antd";
|
||
import zhCN from "antd/locale/zh_CN";
|
||
|
||
function AppConfigProvider({ children }: { children: React.ReactNode }) {
|
||
return (
|
||
<ConfigProvider
|
||
theme={yourTheme}
|
||
locale={zhCN}
|
||
>
|
||
<App>
|
||
<div className="app-container">
|
||
{children}
|
||
</div>
|
||
</App>
|
||
</ConfigProvider>
|
||
);
|
||
}
|
||
|
||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||
return (
|
||
<AppConfigProvider>
|
||
<Component {...pageProps} />
|
||
</AppConfigProvider>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2. 在组件中使用 useApp Hook
|
||
|
||
**正确方式:**
|
||
|
||
```tsx
|
||
import React from 'react';
|
||
import { App, Button } from 'antd';
|
||
|
||
const { useApp } = App;
|
||
|
||
const MyComponent = () => {
|
||
// ✅ 使用 useApp hook 获取 message 实例
|
||
const { message } = useApp();
|
||
|
||
const handleClick = () => {
|
||
message.success('操作成功!');
|
||
message.error('操作失败!');
|
||
message.warning('警告信息!');
|
||
message.info('提示信息!');
|
||
};
|
||
|
||
return (
|
||
<Button onClick={handleClick}>
|
||
点击测试 Message
|
||
</Button>
|
||
);
|
||
};
|
||
|
||
export default MyComponent;
|
||
```
|
||
|
||
**错误方式:**
|
||
|
||
```tsx
|
||
import { message } from 'antd'; // ❌ 不推荐:静态导入
|
||
|
||
const MyComponent = () => {
|
||
const handleClick = () => {
|
||
message.success('操作成功!'); // ❌ 会产生警告
|
||
};
|
||
|
||
return <Button onClick={handleClick}>点击</Button>;
|
||
};
|
||
```
|
||
|
||
## 📋 复制功能使用指南
|
||
|
||
### 1. 简单文本复制
|
||
|
||
使用 `Typography.Paragraph` 的 `copyable` 属性:
|
||
|
||
```tsx
|
||
import { Typography } from 'antd';
|
||
import { App } from 'antd';
|
||
|
||
const { Paragraph } = Typography;
|
||
const { useApp } = App;
|
||
|
||
const SimpleTextCopy = () => {
|
||
const { message } = useApp();
|
||
|
||
const textToCopy = "这是要复制的文本内容";
|
||
|
||
return (
|
||
<Paragraph
|
||
copyable={{
|
||
text: textToCopy,
|
||
onCopy: () => message.success('复制成功!'),
|
||
tooltips: ['点击复制', '复制成功']
|
||
}}
|
||
>
|
||
{textToCopy}
|
||
</Paragraph>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 2. 自定义复制按钮
|
||
|
||
```tsx
|
||
import { Typography, Button } from 'antd';
|
||
import { CopyOutlined } from '@ant-design/icons';
|
||
|
||
const CustomCopyButton = () => {
|
||
const { message } = useApp();
|
||
const copyText = "自定义复制内容";
|
||
|
||
return (
|
||
<Paragraph
|
||
copyable={{
|
||
text: copyText,
|
||
icon: <CopyOutlined />, // 自定义图标
|
||
onCopy: () => message.success('内容已复制到剪贴板'),
|
||
tooltips: ['复制内容', '复制成功']
|
||
}}
|
||
style={{ margin: 0 }}
|
||
>
|
||
{/* 空内容,只显示复制按钮 */}
|
||
</Paragraph>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 3. 复杂复制逻辑(图片 + 文本)
|
||
|
||
对于需要复制图片和文本的复杂场景:
|
||
|
||
```tsx
|
||
import { Button, Tooltip } from 'antd';
|
||
import { CopyOutlined } from '@ant-design/icons';
|
||
|
||
const ComplexCopyFunction = () => {
|
||
const { message } = useApp();
|
||
|
||
const handleComplexCopy = async () => {
|
||
try {
|
||
const textContent = "文本内容";
|
||
|
||
// 尝试复制图片 + 文本
|
||
if (hasImage) {
|
||
try {
|
||
const imageBlob = await fetchImageAsBlob();
|
||
|
||
const clipboardItems = {
|
||
"text/plain": new Blob([textContent], { type: "text/plain" }),
|
||
[imageBlob.type]: imageBlob
|
||
};
|
||
|
||
const clipboardItem = new ClipboardItem(clipboardItems);
|
||
await navigator.clipboard.write([clipboardItem]);
|
||
message.success('文本和图片已复制');
|
||
} catch (imageError) {
|
||
// 降级到仅文本复制
|
||
await navigator.clipboard.writeText(textContent);
|
||
message.success('文本已复制(图片复制失败)');
|
||
}
|
||
} else {
|
||
// 仅文本复制
|
||
await navigator.clipboard.writeText(textContent);
|
||
message.success('文本已复制');
|
||
}
|
||
} catch (error) {
|
||
console.error('复制失败:', error);
|
||
message.error('复制失败');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Tooltip title="复制内容">
|
||
<Button
|
||
icon={<CopyOutlined />}
|
||
onClick={handleComplexCopy}
|
||
>
|
||
复制
|
||
</Button>
|
||
</Tooltip>
|
||
);
|
||
};
|
||
```
|
||
|
||
## 🎯 最佳实践
|
||
|
||
### 1. Message 使用建议
|
||
|
||
```tsx
|
||
const BestPracticeExample = () => {
|
||
const { message } = useApp();
|
||
|
||
// ✅ 推荐:使用 useApp hook
|
||
const showSuccess = () => {
|
||
message.success({
|
||
content: '操作成功!',
|
||
duration: 3, // 显示时长
|
||
key: 'unique-key', // 唯一键,避免重复显示
|
||
});
|
||
};
|
||
|
||
// ✅ 推荐:加载状态处理
|
||
const showLoading = () => {
|
||
message.loading({
|
||
content: '正在处理...',
|
||
key: 'loading-key',
|
||
duration: 0 // 0 表示不自动关闭
|
||
});
|
||
|
||
// 模拟异步操作
|
||
setTimeout(() => {
|
||
message.success({
|
||
content: '处理完成!',
|
||
key: 'loading-key' // 同样的 key 会替换之前的 message
|
||
});
|
||
}, 2000);
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<Button onClick={showSuccess}>成功提示</Button>
|
||
<Button onClick={showLoading}>加载提示</Button>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 2. 复制功能建议
|
||
|
||
```tsx
|
||
const CopyBestPractice = () => {
|
||
const { message } = useApp();
|
||
|
||
// ✅ 推荐:简单文本使用 Paragraph copyable
|
||
const simpleCopy = (
|
||
<Paragraph
|
||
copyable={{
|
||
text: "简单文本",
|
||
onCopy: () => message.success('复制成功'),
|
||
}}
|
||
>
|
||
简单文本复制
|
||
</Paragraph>
|
||
);
|
||
|
||
// ✅ 推荐:复杂逻辑使用自定义函数 + Tooltip
|
||
const complexCopy = (
|
||
<Tooltip title="复制详细信息">
|
||
<Button onClick={handleComplexCopyLogic}>
|
||
复制
|
||
</Button>
|
||
</Tooltip>
|
||
);
|
||
|
||
return (
|
||
<div>
|
||
{simpleCopy}
|
||
{complexCopy}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
## ⚠️ 常见错误
|
||
|
||
### 1. 避免事件冲突
|
||
|
||
```tsx
|
||
// ❌ 错误:在 Paragraph copyable 中嵌套按钮会导致事件冲突
|
||
<Paragraph copyable={{ text: "内容" }}>
|
||
<Button>点击</Button> {/* 会导致点击事件冲突 */}
|
||
</Paragraph>
|
||
|
||
// ✅ 正确:分别处理
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
<Paragraph copyable={{ text: "内容" }}>内容</Paragraph>
|
||
<Button>其他操作</Button>
|
||
</div>
|
||
```
|
||
|
||
### 2. 避免样式冲突
|
||
|
||
```tsx
|
||
// ❌ 错误:fontSize: 0 可能影响可点击区域
|
||
<Paragraph
|
||
copyable={{ text: "内容" }}
|
||
style={{ fontSize: 0 }} // 可能导致无法点击
|
||
>
|
||
内容
|
||
</Paragraph>
|
||
|
||
// ✅ 正确:使用合适的样式
|
||
<Paragraph
|
||
copyable={{ text: "内容" }}
|
||
style={{ margin: 0, lineHeight: 1 }}
|
||
>
|
||
内容
|
||
</Paragraph>
|
||
```
|
||
|
||
## 🔧 调试技巧
|
||
|
||
### 1. 检查 App 组件配置
|
||
|
||
确保组件在 `App` 组件内部:
|
||
|
||
```tsx
|
||
// 在浏览器控制台检查
|
||
console.log('App context:', React.useContext(AppContext));
|
||
```
|
||
|
||
### 2. 验证复制功能
|
||
|
||
```tsx
|
||
const testCopy = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText('测试');
|
||
console.log('复制功能正常');
|
||
} catch (error) {
|
||
console.error('复制功能异常:', error);
|
||
}
|
||
};
|
||
```
|
||
|
||
## 📚 参考资料
|
||
|
||
- [Ant Design App 组件文档](https://ant.design/components/app-cn)
|
||
- [Ant Design Typography 组件文档](https://ant.design/components/typography-cn)
|
||
- [Ant Design Message 组件文档](https://ant.design/components/message-cn)
|
||
- [Web Clipboard API](https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard)
|
||
|
||
## 📝 更新日志
|
||
|
||
- **v1.0.0** (2024-12-XX): 初始版本,包含基础使用方法和最佳实践
|
||
|
||
---
|
||
|
||
> 💡 **提示**:遵循本文档的最佳实践,可以避免常见的警告和错误,提供更好的用户体验。 |