这是一款用 React 18 UMD + 原生 `Date` 对象写的极简月历组件:顶部一行显示当前年份、月份和左右翻月箭头,点年份/月份文字直接跳回今天;下方 7 列网格展示星期缩写和整月日期,今天用高亮圆点标记,跨月补齐的日期灰显不可选。组件全部靠 `useState` + `useMemo` 管理年月,没有任何第三方依赖,脚本仅 168 行,可以直接扔进任何 CDN 引入 React 的静态页里跑起来。适合作为业务日历、预约系统、活动打卡的基础壳。
这款极简日历组件的核心亮点
月份数据全部由 `new Date(year, month, 0).getDate()` 现算:给 JS Date 传第 0 天等价于「上个月最后一天」,一句话拿到当月天数,无需引入 date-fns 或 dayjs。
跨月补齐逻辑清晰:拿到当月 1 号星期几作为 `startDay`,从上月末尾切 `startDay` 天补前面;拿到当月末星期几作为 `endDay`,从下月开头切 `6 - endDay` 天补后面,正好铺满 6×7 网格。
今日高亮不比对 Date 对象而是拼字符串 `${year}${month}${date}` 做全等,避免时区导致的一日之差;`useMemo` 缓存了这个拼串,年月切换时不会每次重算。
翻月的加减法自己写:月份为 1 时上月切到去年 12 月、月份为 12 时下月切到明年 1 月,边界都在 `getPrevYearMonth` / `getNextYearMonth` 两个纯函数里,方便单测。
React Hooks 在这套日历里的三个应用取舍
其一,`year` 和 `month` 分开存两个 `useState`,而不是合成一个对象,改起来更直接、也避免 setState 合并时的额外闭包问题。其二,`todayStr` 用 `useMemo` 依赖 today,只有一次计算——虽然 today 在这版里没变,但为将来做「切日切换」预留了。其三,DOM 全部用 `React.createElement` 而非 JSX,好处是不需要 Babel/构建就能跑,缺点是可读性差一些,源码中均有注释可对照。
接入自己项目要改什么
要接后端事件(把有事件的日期标红),在 `ActiveMonth` 的 `visibleMonth.map` 里给对应 el 加一个 `className: "has-event"` 判断即可,事件数据从 props 传入。要改语言,把 `week` 数组换成中文 `["日","一","二","三","四","五","六"]`。要国际化为「周一开头」,把 `week` 和 `startDay/endDay` 的偏移都统一调整(当前是周日开头)。要接可选日期回调,给 `.date` 加 onClick 并向上通知即可,逻辑很短。
浏览器兼容性:需支持 ES2015 及以上,主流浏览器均可;React 18 用 UMD 版本从 CDN 加载,可离线替换为本地文件。详细运行方法请参见压缩包内的帮助文档。
常见问题
能改成周历(一周一行)显示吗
可以。改造 `visibleMonth` 生成逻辑,按当前日所在周的周日到周六裁 7 天即可;样式部分只需调 `.dates-wrapper` 的 grid 行数。
怎么让日期支持点击回调
在 `.date` 元素上加 `onClick: () => props.onSelect(date)`,把日期通过 props 上抛到父组件,再由业务代码决定弹窗、跳详情或高亮。
要显示农历/节假日怎么办
推荐引入 lunar-javascript 或 solarlunar 库,在渲染每个 `.date` 时按 year/month/date 查一次,把返回的农历/节日字符串放到 `
` 的二级子元素里。
可以接入 Vue 或 vanilla JS 项目吗
不能直接接入,但算法层(`getMonthDetail` / `getPrev...` / `getNext...`)是纯函数,可原样搬到 Vue、Svelte 或 vanilla JS 里,只有渲染层需要重写。
适合放在哪些页面
适合日程管理、预约挂号、活动打卡、后台管理系统的日期筛选器、SaaS 应用的日历视图。下载源码后可直接放进 React 项目作为业务基础日历。