React + Ant Design 日期选择器避免显示“Invalid Date“的解决方案
目录
- 项目场景:
- 问题描述
- 完整解决方案
- 原因分析:
项目场景:
当前例子是以React 18 + Ant Design + tsx
问题描述
在从API获取日期数据或用户输入日期时,当遇到无效日期字符串时,页面会显示"Invalid Date"错误:
API返回的日期字符串可能格式不正确
用户可能手动输入无效日期
组件没有对日期有效性进行验证
无效日期尝试格式化时出现错误
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 = {// 模拟获取开通时间的APIgetActivationDate: (): 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);});},// 模拟保存开通时间的APIsaveActivationDate: (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);}};// 保存日期到APIconst 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><DatePickervalue={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;
完整解决方案
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><DatePickervalue={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前没有验证日期是否有效
用户体验差:无效日期显示原生错误信息,没有视觉反馈
缺乏安全解析:没有封装安全的日期解析函数