文章目录
引言
在现代前端开发中,日期和时间处理是一个常见且重要的需求。无论是显示文章发布时间、处理用户输入的日期,还是实现复杂的时间计算,我们都需要一个强大而轻量的日期库。Day.js 作为 Moment.js 的现代替代品,以其轻量、API友好和不可变性等特点,成为了 React 项目中处理日期时间的首选工具。
什么是Day.js?
Day.js 是一个轻量级的 JavaScript 日期库,专为现代浏览器设计。它提供了与 Moment.js 类似的 API,但体积仅有 2KB(压缩后),相比 Moment.js 的 67KB 有着显著的优势。
Day.js的核心特性
- 轻量级:仅2KB的体积
- 不可变性:所有API都返回新的实例
- 链式调用:支持方法链式调用
- 国际化:支持多语言
- 插件系统:可按需加载功能
- TypeScript支持:完整的类型定义
安装和基础配置
安装Day.js
# 使用npm
npm install dayjs
# 使用yarn
yarn add dayjs
# 使用pnpm
pnpm add dayjs
基础导入和使用
import dayjs from 'dayjs'
// 创建当前时间
const now = dayjs()
// 创建指定时间
const specificDate = dayjs('2024-01-01')
const fromTimestamp = dayjs(1640995200000)
在React中的基础使用
1. 显示格式化日期
import React from 'react'
import dayjs from 'dayjs'
const DateDisplay = ({ date }) => {
return (
<div>
<p>完整日期: {dayjs(date).format('YYYY-MM-DD HH:mm:ss')}</p>
<p>相对时间: {dayjs(date).fromNow()}</p>
<p>友好格式: {dayjs(date).format('MMMM D, YYYY')}</p>
</div>
)
}
2. 实时时钟组件
import React, { useState, useEffect } from 'react'
import dayjs from 'dayjs'
const RealTimeClock = () => {
const [currentTime, setCurrentTime] = useState(dayjs())
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(dayjs())
}, 1000)
return () => clearInterval(timer)
}, [])
return (
<div className="clock">
<h2>{currentTime.format('HH:mm:ss')}</h2>
<p>{currentTime.format('YYYY年MM月DD日 dddd')}</p>
</div>
)
}
常用插件配置
Day.js 采用插件系统来扩展功能,常用的插件包括:
1. 相对时间插件
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
// 使用相对时间
const date = dayjs('2024-01-01')
console.log(date.fromNow()) // "5个月前"
2. 高级格式化插件
import advancedFormat from 'dayjs/plugin/advancedFormat'
dayjs.extend(advancedFormat)
// 使用高级格式化
const date = dayjs()
console.log(date.format('Q')) // 季度
console.log(date.format('Do')) // 带序数的日期
3. 时区处理插件
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
dayjs.extend(utc)
dayjs.extend(timezone)
// 时区转换
const utcDate = dayjs.utc('2024-01-01 12:00:00')
const localDate = utcDate.tz('Asia/Shanghai')
实战案例:博客文章时间组件
让我们创建一个完整的博客文章时间显示组件:
import React, { useMemo } from 'react'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
const ArticleTime = ({
publishTime,
updateTime,
showRelative = true,
showUpdate = true
}) => {
const publishDate = useMemo(() => dayjs(publishTime), [publishTime])
const updateDate = useMemo(() =>
updateTime ? dayjs(updateTime) : null,
[updateTime]
)
const isRecent = useMemo(() =>
publishDate.isAfter(dayjs().subtract(7, 'day')),
[publishDate]
)
const isUpdated = useMemo(() =>
updateDate && updateDate.isAfter(publishDate.add(1, 'day')),
[publishDate, updateDate]
)
return (
<div className="article-time">
<div className="publish-time">
<span className="label">发布时间: </span>
<time dateTime={publishDate.toISOString()}>
{showRelative ? publishDate.fromNow() : publishDate.format('YYYY-MM-DD')}
</time>
{isRecent && <span className="badge new">新</span>}
</div>
{showUpdate && isUpdated && (
<div className="update-time">
<span className="label">最后更新: </span>
<time dateTime={updateDate.toISOString()}>
{showRelative ? updateDate.fromNow() : updateDate.format('YYYY-MM-DD')}
</time>
<span className="badge updated">已更新</span>
</div>
)}
</div>
)
}
export default ArticleTime
高级应用场景
1. 日期范围选择器
import React, { useState } from 'react'
import dayjs from 'dayjs'
import isBetween from 'dayjs/plugin/isBetween'
dayjs.extend(isBetween)
const DateRangePicker = ({ onRangeChange }) => {
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const handleRangeChange = () => {
if (startDate && endDate) {
const start = dayjs(startDate)
const end = dayjs(endDate)
if (start.isAfter(end)) {
alert('开始日期不能晚于结束日期')
return
}
const daysDiff = end.diff(start, 'day')
onRangeChange({
start: start.format('YYYY-MM-DD'),
end: end.format('YYYY-MM-DD'),
days: daysDiff + 1
})
}
}
return (
<div className="date-range-picker">
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
max={endDate || dayjs().format('YYYY-MM-DD')}
/>
<span>到</span>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
min={startDate}
max={dayjs().format('YYYY-MM-DD')}
/>
<button onClick={handleRangeChange}>确认</button>
</div>
)
}
2. 倒计时组件
import React, { useState, useEffect } from 'react'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
dayjs.extend(duration)
const Countdown = ({ targetDate, onComplete }) => {
const [timeLeft, setTimeLeft] = useState(null)
useEffect(() => {
const target = dayjs(targetDate)
const updateCountdown = () => {
const now = dayjs()
const diff = target.diff(now)
if (diff <= 0) {
setTimeLeft(null)
onComplete && onComplete()
return
}
const duration = dayjs.duration(diff)
setTimeLeft({
days: Math.floor(duration.asDays()),
hours: duration.hours(),
minutes: duration.minutes(),
seconds: duration.seconds()
})
}
updateCountdown()
const timer = setInterval(updateCountdown, 1000)
return () => clearInterval(timer)
}, [targetDate, onComplete])
if (!timeLeft) {
return <div className="countdown finished">时间到!</div>
}
return (
<div className="countdown">
<div className="time-unit">
<span className="number">{timeLeft.days}</span>
<span className="label">天</span>
</div>
<div className="time-unit">
<span className="number">{timeLeft.hours}</span>
<span className="label">时</span>
</div>
<div className="time-unit">
<span className="number">{timeLeft.minutes}</span>
<span className="label">分</span>
</div>
<div className="time-unit">
<span className="number">{timeLeft.seconds}</span>
<span className="label">秒</span>
</div>
</div>
)
}
性能优化技巧
1. 使用useMemo缓存计算结果
import React, { useMemo } from 'react'
import dayjs from 'dayjs'
const OptimizedDateComponent = ({ dates }) => {
const sortedDates = useMemo(() => {
return dates
.map(date => dayjs(date))
.sort((a, b) => a.valueOf() - b.valueOf())
.map(date => date.format('YYYY-MM-DD'))
}, [dates])
return (
<ul>
{sortedDates.map((date, index) => (
<li key={index}>{date}</li>
))}
</ul>
)
}
2. 避免重复创建实例
// 不推荐
const BadExample = ({ timestamp }) => {
return (
<div>
<p>{dayjs(timestamp).format('YYYY-MM-DD')}</p>
<p>{dayjs(timestamp).format('HH:mm:ss')}</p>
<p>{dayjs(timestamp).fromNow()}</p>
</div>
)
}
// 推荐
const GoodExample = ({ timestamp }) => {
const date = useMemo(() => dayjs(timestamp), [timestamp])
return (
<div>
<p>{date.format('YYYY-MM-DD')}</p>
<p>{date.format('HH:mm:ss')}</p>
<p>{date.fromNow()}</p>
</div>
)
}
最佳实践
1. 统一的日期格式管理
// utils/dateFormats.js
export const DATE_FORMATS = {
DISPLAY: 'YYYY年MM月DD日',
INPUT: 'YYYY-MM-DD',
DATETIME: 'YYYY-MM-DD HH:mm:ss',
TIME: 'HH:mm:ss',
MONTH: 'YYYY-MM',
YEAR: 'YYYY'
}
// 使用
import { DATE_FORMATS } from './utils/dateFormats'
const formattedDate = dayjs(date).format(DATE_FORMATS.DISPLAY)
2. 创建自定义Hook
import { useState, useEffect } from 'react'
import dayjs from 'dayjs'
export const useCurrentTime = (updateInterval = 1000) => {
const [currentTime, setCurrentTime] = useState(dayjs())
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(dayjs())
}, updateInterval)
return () => clearInterval(timer)
}, [updateInterval])
return currentTime
}
// 使用
const MyComponent = () => {
const currentTime = useCurrentTime()
return <div>{currentTime.format('HH:mm:ss')}</div>
}
3. 错误处理
const SafeDateComponent = ({ dateString }) => {
const formatDate = (date) => {
try {
const dayObj = dayjs(date)
if (!dayObj.isValid()) {
return '无效日期'
}
return dayObj.format('YYYY-MM-DD')
} catch (error) {
console.error('日期格式化错误:', error)
return '日期格式错误'
}
}
return <div>{formatDate(dateString)}</div>
}
常见问题和解决方案
1. 时区问题
// 始终使用UTC时间进行存储和传输
const saveDate = dayjs().utc().toISOString()
// 在显示时转换为本地时间
const displayDate = dayjs.utc(saveDate).local()
2. 本地化问题
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import 'dayjs/locale/en'
// 根据用户设置切换语言
const setLanguage = (lang) => {
dayjs.locale(lang)
}
3. 服务端渲染(SSR)问题
import { useEffect, useState } from 'react'
import dayjs from 'dayjs'
const SSRSafeDateComponent = ({ date }) => {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <div>加载中...</div>
}
return <div>{dayjs(date).format('YYYY-MM-DD')}</div>
}