React函数组件结合状态驱动的开发模式,可以让日历渲染逻辑更清晰,避免直接操作DOM带来的维护成本。传统的日历实现往往需要手动创建、修改DOM节点,而状态驱动方案只需要更新对应的状态,React会自动完成视图的更新。

核心实现思路
日历渲染的核心是先计算出当前月份需要展示的所有日期,再通过状态管理这些日期的展示逻辑。主要包含三个部分:状态定义、日期计算、视图渲染。
1. 状态定义
首先需要定义控制日历的核心状态,包括当前选中的年份、月份,以及需要展示的日期列表。
import { useState, useEffect } from 'react';
const Calendar = () => {
// 当前选中的年份
const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
// 当前选中的月份,0代表1月,11代表12月
const [currentMonth, setCurrentMonth] = useState(new Date().getMonth());
// 当前月份需要展示的日期数组
const [dateList, setDateList] = useState([]);
// 当前选中的日期
const [selectedDate, setSelectedDate] = useState(null);
return (
<div className="calendar-container">
{/* 后续渲染内容 */}
</div>
);
};
export default Calendar;
2. 日期计算逻辑
需要根据当前选中的年份和月份,计算出该月的第一天是周几,以及该月的总天数,同时补充上个月末尾和下个月开头的日期,保证日历完整显示6行。
// 计算指定年月的天数
const getMonthDays = (year, month) => {
return new Date(year, month + 1, 0).getDate();
};
// 计算指定年月第一天是周几,0代表周日,6代表周六
const getFirstDayOfWeek = (year, month) => {
return new Date(year, month, 1).getDay();
};
// 生成日期列表的方法
const generateDateList = () => {
const monthDays = getMonthDays(currentYear, currentMonth);
const firstDay = getFirstDayOfWeek(currentYear, currentMonth);
const list = [];
// 补充上个月的末尾日期
const prevMonthDays = getMonthDays(currentYear, currentMonth - 1);
for (let i = firstDay - 1; i >= 0; i--) {
list.push({
date: prevMonthDays - i,
isCurrentMonth: false,
fullDate: `${currentYear}-${currentMonth === 0 ? currentYear - 1 : currentYear}-${currentMonth === 0 ? 12 : currentMonth}`
});
}
// 补充当前月的日期
for (let i = 1; i <= monthDays; i++) {
list.push({
date: i,
isCurrentMonth: true,
fullDate: `${currentYear}-${currentMonth + 1}-${i}`
});
}
// 补充下个月的开头日期,保证总长度为42(6行*7列)
const remainCount = 42 - list.length;
for (let i = 1; i <= remainCount; i++) {
list.push({
date: i,
isCurrentMonth: false,
fullDate: `${currentYear}-${currentMonth === 11 ? currentYear + 1 : currentYear}-${currentMonth === 11 ? 1 : currentMonth + 2}`
});
}
setDateList(list);
};
// 年份或月份变化时重新生成日期列表
useEffect(() => {
generateDateList();
}, [currentYear, currentMonth]);
3. 视图渲染与交互
基于生成的日期列表渲染日历视图,同时实现月份切换、日期选中、高亮今天等交互功能,所有操作都通过修改状态实现,不需要操作DOM。
// 星期标题
const weekTitles = ['日', '一', '二', '三', '四', '五', '六'];
// 切换到上一个月
const goToPrevMonth = () => {
if (currentMonth === 0) {
setCurrentYear(currentYear - 1);
setCurrentMonth(11);
} else {
setCurrentMonth(currentMonth - 1);
}
};
// 切换到下一个月
const goToNextMonth = () => {
if (currentMonth === 11) {
setCurrentYear(currentYear + 1);
setCurrentMonth(0);
} else {
setCurrentMonth(currentMonth + 1);
}
};
// 选中日期
const handleDateClick = (item) => {
setSelectedDate(item.fullDate);
};
// 判断是否是今天
const isToday = (item) => {
const today = new Date();
const todayStr = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
return item.fullDate === todayStr;
};
return (
<div className="calendar-container">
{/* 头部控制栏 */}
<div className="calendar-header">
<button onClick={goToPrevMonth}>上个月</button>
<span className="current-month">{currentYear}年{currentMonth + 1}月</span>
<button onClick={goToNextMonth}>下个月</button>
</div>
{/* 星期标题行 */}
<div className="week-row">
{weekTitles.map((title, index) => (
<div key={index} className="week-item">{title}</div>
))}
</div>
{/* 日期网格 */}
<div className="date-grid">
{dateList.map((item, index) => (
<div
key={index}
className={`date-item ${item.isCurrentMonth ? '' : 'other-month'} ${isToday(item) ? 'today' : ''} ${selectedDate === item.fullDate ? 'selected' : ''}`}
onClick={() => handleDateClick(item)}
>
{item.date}
</div>
))}
</div>
</div>
);
样式补充说明
可以通过CSS为日历添加基础样式,这里提供简单的样式参考,不需要操作DOM,只需要定义类名即可。
.calendar-container {
width: 400px;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.current-month {
font-size: 18px;
font-weight: bold;
}
.week-row {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
margin-bottom: 8px;
}
.week-item {
padding: 8px 0;
font-weight: bold;
}
.date-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.date-item {
padding: 12px 0;
text-align: center;
cursor: pointer;
border-radius: 4px;
}
.date-item:hover {
background-color: #f5f5f5;
}
.other-month {
color: #ccc;
}
.today {
background-color: #e6f7ff;
color: #1890ff;
}
.selected {
background-color: #1890ff;
color: #fff;
}
方案优势
- 不需要手动操作DOM节点,所有视图更新由React自动完成,减少出错概率
- 状态逻辑和视图逻辑分离,代码可读性和可维护性更高
- 扩展功能更方便,比如添加节假日标记、日程展示等功能,只需要新增对应的状态和渲染逻辑即可
- 符合React的函数式编程思想,充分利用hooks的能力管理组件状态
这种状态驱动的日历实现方案,完全避免了原生DOM操作的繁琐,是React函数组件开发日历功能的推荐方式。开发者可以根据实际需求扩展更多功能,比如日期范围选择、农历显示等,核心思路都是基于状态管理实现视图更新。