目录
项目场景:
当前例子是以React 18 + Ant Design + tsx
问题描述
在从API获取日期数据或用户输入日期时,当遇到无效日期字符串时,页面会显示"Invalid Date"错误:
API返回的日期字符串可能格式不正确
用户可能手动输入无效日期
组件没有对日期有效性进行验证
无效日期尝试格式化时出现错误
ts
import React, { useState, useEffect } from 'react';
import { DatePicker, ConfigProvider, Button, message } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'antd/dist/reset.css';
// 模拟API接口
const mockApi = {
// 模拟获取开通时间的API
getActivationDate: (): Promise<string> => {
return new Promise((resolve, reject) => {
// 模拟网络延迟
setTimeout(() => {
// 随机返回有效或无效日期
const random = Math.random();
if (random < 0.3) {
// 返回有效日期
resolve('2023-10-15');
} else if (random < 0.6) {
// 返回无效日期格式
resolve('2023-10-15T08:30:00Z'); // ISO格式,但dayjs可能解析错误
} else {
// 返回无效日期字符串
resolve('无效日期');
}
}, 800);
});
},
// 模拟保存开通时间的API
saveActivationDate: (date: string): Promise<boolean> => {
return new Promise((resolve) => {
setTimeout(() => {
// 检查日期是否有效
const isValid = dayjs(date).isValid();
resolve(isValid);
}, 500);
});
}
};
// 有问题的日期选择器组件
const ProblematicDatePicker = () => {
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(null);
const [apiDate, setApiDate] = useState<dayjs.Dayjs | null>(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
// 从API获取日期
const fetchDateFromApi = async () => {
setLoading(true);
try {
const dateString = await mockApi.getActivationDate();
// 问题:直接转换API返回的字符串,没有验证
const date = dayjs(dateString);
setApiDate(date);
message.info(`从API获取到日期: ${dateString}`);
} catch (error) {
message.error('获取日期失败');
} finally {
setLoading(false);
}
};
// 保存日期到API
const saveDateToApi = async () => {
if (!selectedDate) {
message.warning('请先选择日期');
return;
}
setSaving(true);
try {
// 问题:直接使用dayjs对象,没有验证有效性
const success = await mockApi.saveActivationDate(selectedDate.format());
if (success) {
message.success('日期保存成功');
} else {
message.error('保存失败:日期无效');
}
} catch (error) {
message.error('保存日期失败');
} finally {
setSaving(false);
}
};
// 有问题的日期格式化函数 - 直接转换,没有验证
const formatDate = (date: dayjs.Dayjs | null) => {
if (!date) return '无日期';
// 问题:没有验证日期有效性
return date.format('YYYY年MM月DD日');
};
// 组件加载时获取API日期
useEffect(() => {
fetchDateFromApi();
}, []);
return (
<ConfigProvider locale={zhCN}>
<div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto', border: '1px solid #f0f0f0', borderRadius: '8px' }}>
<h2 style={{ textAlign: 'center', color: '#ff4d4f' }}>有问题的日期选择器</h2>
<div style={{ margin: '20px 0' }}>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>从API获取的开通时间:</div>
<div style={{
padding: '10px',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
minHeight: '20px',
color: apiDate && !apiDate.isValid() ? '#ff4d4f' : 'inherit'
}}>
{formatDate(apiDate)}
{apiDate && !apiDate.isValid() && (
<span style={{ color: '#ff4d4f', marginLeft: '10px' }}>⚠️ 无效日期</span>
)}
</div>
<Button
type="primary"
onClick={fetchDateFromApi}
loading={loading}
style={{ marginTop: '10px' }}
>
重新获取API日期
</Button>
</div>
<div style={{ margin: '20px 0' }}>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>设置开通时间:</div>
<DatePicker
value={selectedDate}
onChange={(date) => setSelectedDate(date)}
placeholder="请选择日期"
style={{ width: '100%' }}
// 问题:没有处理无效日期输入
/>
<div style={{
marginTop: '10px',
padding: '10px',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
minHeight: '20px',
color: selectedDate && !selectedDate.isValid() ? '#ff4d4f' : 'inherit'
}}>
{formatDate(selectedDate)}
{selectedDate && !selectedDate.isValid() && (
<span style={{ color: '#ff4d4f', marginLeft: '10px' }}>⚠️ 无效日期</span>
)}
</div>
<Button
type="primary"
onClick={saveDateToApi}
loading={saving}
style={{ marginTop: '10px', width: '100%' }}
>
保存开通时间
</Button>
</div>
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#fff2f0', borderRadius: '4px' }}>
<h3>问题说明:</h3>
<ul style={{ color: '#ff4d4f' }}>
<li>API返回的日期字符串可能无效,但组件没有验证</li>
<li>用户选择的日期没有有效性检查</li>
<li>无效日期会显示"Invalid Date"或格式错误</li>
<li>保存到API前没有验证日期有效性</li>
</ul>
</div>
</div>
</ConfigProvider>
);
};
export default ProblematicDatePicker;
完整解决方案
typescript
import React, { useState, useEffect } from 'react';
import { DatePicker, ConfigProvider, Button, message, Tag, Alert } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'antd/dist/reset.css';
// 修复点1:添加自定义日期解析函数
const safeDayjs = (dateString: string): dayjs.Dayjs | null => {
try {
const date = dayjs(dateString);
return date.isValid() ? date : null;
} catch (error) {
return null;
}
};
// 模拟API接口(保持不变)
const mockApi = {
getActivationDate: (): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => {
const random = Math.random();
if (random < 0.3) {
resolve('2023-10-15');
} else if (random < 0.6) {
resolve('2023-10-15T08:30:00Z');
} else {
resolve('无效日期');
}
}, 800);
});
},
saveActivationDate: (date: string): Promise<boolean> => {
return new Promise((resolve) => {
setTimeout(() => {
const isValid = dayjs(date).isValid();
resolve(isValid);
}, 500);
});
}
};
// 修复后的日期选择器组件
const FixedDatePicker = () => {
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(null);
const [apiDate, setApiDate] = useState<dayjs.Dayjs | null>(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [apiError, setApiError] = useState<string | null>(null); // 修复点2:添加API错误状态
// 修复点3:安全的日期格式化函数
const formatDate = (date: dayjs.Dayjs | null) => {
if (!date) return <Tag color="default">无日期</Tag>;
// 修复点4:验证日期有效性
if (!date.isValid()) {
return <Tag color="error">无效日期</Tag>;
}
return <Tag color="blue">{date.format('YYYY年MM月DD日')}</Tag>;
};
// 修复点5:安全的日期获取函数
const fetchDateFromApi = async () => {
setLoading(true);
setApiError(null);
try {
const dateString = await mockApi.getActivationDate();
// 修复点6:使用安全的日期解析函数
const date = safeDayjs(dateString);
if (date) {
setApiDate(date);
message.success(`成功获取有效日期: ${date.format('YYYY-MM-DD')}`);
} else {
setApiDate(null);
setApiError(`API返回无效日期: ${dateString}`);
message.warning(`API返回无效日期: ${dateString}`);
}
} catch (error) {
setApiError('获取日期失败');
message.error('获取日期失败');
} finally {
setLoading(false);
}
};
// 修复点7:安全的日期保存函数
const saveDateToApi = async () => {
if (!selectedDate) {
message.warning('请先选择日期');
return;
}
// 修复点8:保存前验证日期有效性
if (!selectedDate.isValid()) {
message.error('无法保存:选择的日期无效');
return;
}
setSaving(true);
try {
const success = await mockApi.saveActivationDate(selectedDate.format());
if (success) {
message.success('日期保存成功');
} else {
message.error('保存失败:API验证日期无效');
}
} catch (error) {
message.error('保存日期失败');
} finally {
setSaving(false);
}
};
// 修复点9:处理日期选择变化
const handleDateChange = (date: dayjs.Dayjs | null) => {
if (date && !date.isValid()) {
// 修复点10:对无效日期提供即时反馈
message.warning('选择的日期无效,请重新选择');
setSelectedDate(null);
return;
}
setSelectedDate(date);
};
// 组件加载时获取API日期
useEffect(() => {
fetchDateFromApi();
}, []);
return (
<ConfigProvider locale={zhCN}>
<div style={{
padding: '20px',
maxWidth: '500px',
margin: '0 auto',
border: '1px solid #e8e8e8',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<h2 style={{ textAlign: 'center', color: '#52c41a' }}>修复后的日期选择器</h2>
{/* 修复点11:添加API错误提示 */}
{apiError && (
<Alert
message={apiError}
type="warning"
showIcon
style={{ marginBottom: '20px' }}
/>
)}
<div style={{ margin: '20px 0' }}>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>从API获取的开通时间:</div>
<div style={{
padding: '10px',
backgroundColor: apiDate && apiDate.isValid() ? '#f6ffed' : '#fff2f0',
border: `1px solid ${apiDate && apiDate.isValid() ? '#b7eb8f' : '#ffccc7'}`,
borderRadius: '4px',
minHeight: '20px',
}}>
{formatDate(apiDate)}
</div>
<Button
type="primary"
onClick={fetchDateFromApi}
loading={loading}
style={{ marginTop: '10px' }}
>
重新获取API日期
</Button>
</div>
<div style={{ margin: '20px 0' }}>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>设置开通时间:</div>
<DatePicker
value={selectedDate}
onChange={handleDateChange} // 修复点12:使用安全的日期变更处理
placeholder="请选择日期"
style={{ width: '100%' }}
// 修复点13:添加日期格式化和验证
format="YYYY-MM-DD"
disabledDate={(current) => {
// 可选:禁用今天之后的日期
return current && current > dayjs().endOf('day');
}}
/>
<div style={{
marginTop: '10px',
padding: '10px',
backgroundColor: selectedDate && selectedDate.isValid() ? '#f6ffed' : '#fff2f0',
border: `1px solid ${selectedDate && selectedDate.isValid() ? '#b7eb8f' : '#ffccc7'}`,
borderRadius: '4px',
minHeight: '20px',
}}>
{formatDate(selectedDate)}
</div>
<Button
type="primary"
onClick={saveDateToApi}
loading={saving}
style={{ marginTop: '10px', width: '100%' }}
// 修复点14:禁用无效日期的保存按钮
disabled={!selectedDate || !selectedDate.isValid()}
>
保存开通时间
</Button>
</div>
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#e6f7ff', borderRadius: '4px' }}>
<h3>修复说明:</h3>
<ul style={{ color: '#1890ff' }}>
<li>添加了安全的日期解析函数 <code>safeDayjs</code></li>
<li>所有日期操作前都验证有效性</li>
<li>对无效日期提供友好的错误提示</li>
<li>使用Ant Design的Tag组件增强视觉反馈</li>
<li>添加了API错误状态处理</li>
<li>保存按钮在日期无效时禁用</li>
</ul>
</div>
</div>
</ConfigProvider>
);
};
export default FixedDatePicker;
原因分析:
直接解析未验证的日期字符串:
直接使用 dayjs(dateString)而没有验证日期有效性
缺乏错误处理:没有对无效日期提供友好的错误提示
缺少有效性检查:在保存到API前没有验证日期是否有效
用户体验差:无效日期显示原生错误信息,没有视觉反馈
缺乏安全解析:没有封装安全的日期解析函数