如何使用样式化组件创建一个简单的 React 日历解决方案说明结论

2025-06-11

如何使用样式组件创建简单的 React 日历

解决方案

解释

结论

访问我的博客查看原始帖子:如何使用样式组件创建简单的 React 日历

我发现制作在 Web 开发中广泛使用的小组件非常有趣。当我还是一名初级 Web 开发者时,如果需要构建一些功能,我倾向于在线搜索库或插件。实现起来可能很困难,因为我没有尝试去思考它的实际工作原理,而且不得不依赖编写糟糕的文档。有时,定制也很困难,因为很难理解作者为什么要以他们的方式设计插件。

日历是最常见的例子之一。网上有很多插件,但很少有真正教你如何使用的。我在上一家公司做初级开发时,被要求定制一个日历,并集成一些业务需求,但我在网上找到的所有库都无法满足我的需求。后来我意识到,为什么不从头开始构建自己的日历呢?

这并不难。让我们使用ReactStyled Component来实现吧!

解决方案

如果您希望快速实现而不阅读我的解释,可以在simple-react-calendar中找到最终实现。

import * as React from 'react';
import { useState, useEffect } from 'react';
import styled, { css } from 'styled-components';

const Frame = styled.div`
  width: 400px;
  border: 1px solid lightgrey;
  box-shadow: 2px 2px 2px #eee;
`;

const Header = styled.div`
  font-size: 18px;
  font-weight: bold;
  padding: 10px 10px 5px 10px;
  display: flex;
  justify-content: space-between;
  background-color: #f5f6fa;
`;

const Button = styled.div`
  cursor: pointer;
`;

const Body = styled.div`
  width: 100%;
  display: flex;
  flex-wrap: wrap;
`;

const Day = styled.div`
  width: 14.2%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;

  ${props =>
    props.isToday &&
    css`
      border: 1px solid #eee;
    `}

  ${props =>
    props.isSelected &&
    css`
      background-color: #eee;
    `}
`;

export function Calendar() {
  const DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const DAYS_OF_THE_WEEK = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'];
  const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];

  const today = new Date();
  const [date, setDate] = useState(today);
  const [day, setDay] = useState(date.getDate());
  const [month, setMonth] = useState(date.getMonth());
  const [year, setYear] = useState(date.getFullYear());
  const [startDay, setStartDay] = useState(getStartDayOfMonth(date));

  useEffect(() => {
    setDay(date.getDate());
    setMonth(date.getMonth());
    setYear(date.getFullYear());
    setStartDay(getStartDayOfMonth(date));
  }, [date]);

  function getStartDayOfMonth(date: Date) {
    return new Date(date.getFullYear(), date.getMonth(), 1).getDay();
  }

  function isLeapYear(year: number) {
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  }

  const days = isLeapYear(date.getFullYear()) ? DAYS_LEAP : DAYS;

  return (
    <Frame>
      <Header>
        <Button onClick={() => setDate(new Date(year, month - 1, day))}>Prev</Button>
        <div>
          {MONTHS[month]} {year}
        </div>
        <Button onClick={() => setDate(new Date(year, month + 1, day))}>Next</Button>
      </Header>
      <Body>
        {DAYS_OF_THE_WEEK.map(d => (
          <Day key={d}>
            <strong>{d}</strong>
          </Day>
        ))}
        {Array(days[month] + (startDay - 1))
          .fill(null)
          .map((_, index) => {
            const d = index - (startDay - 2);
            return (
              <Day
                key={index}
                isToday={d === today.getDate()}
                isSelected={d === day}
                onClick={() => setDate(new Date(year, month, d))}
              >
                {d > 0 ? d : ''}
              </Day>
            );
          })}
      </Body>
    </Frame>
  );
}

Enter fullscreen mode Exit fullscreen mode

解释

初始化日历组件

组件的初始化比较简单,首先导入必要的库,然后创建一个名为 的函数组件Calendar

在组件内部,我们div暂时返回一个空的,并添加一些常量,它们是

  • DAYS:正常年份中每个月的天数数组
  • DAYS_LEAP:闰年每个月的天数数组
  • DAYS_OF_THE_WEEK:一周中各天的名称数组
  • MONTHS:月份名称数组
import * as React from 'react';
import { useState, useEffect } from 'react';
import styled, { css } from 'styled-components';

export function Calendar() {
  const DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const DAYS_OF_THE_WEEK = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'];
  const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];

  // Will be implemented below

  return (
    <div />
  );
}
Enter fullscreen mode Exit fullscreen mode

识别组件布局

现在让我们来决定日历组件的布局。由于我们构建的是一个基础的日历组件,所以我们只需要一个标题为当前月份和年份的标题,以及一个上个月按钮和一个下个月按钮。

至于正文部分,它由两部分组成,一行表示星期几,多行表示实际日期。

日历组件的结构

现在,让我们使用样式组件创建这些部分并将它们放在日历功能组件上方。

const Frame = styled.div`
  width: 400px;
  border: 1px solid lightgrey;
  box-shadow: 2px 2px 2px #eee;
`;

const Header = styled.div`
  font-size: 18px;
  font-weight: bold;
  padding: 10px 10px 5px 10px;
  display: flex;
  justify-content: space-between;
  background-color: #f5f6fa;
`;

const Button = styled.div`
  cursor: pointer;
`;

const Body = styled.div`
  width: 100%;
  display: flex;
  flex-wrap: wrap;
`;

const Day = styled.div`
  width: 14.2%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;

  ${props =>
    props.isToday &&
    css`
      border: 1px solid #eee;
    `}

  ${props =>
    props.isSelected &&
    css`
      background-color: #eee;
    `}
`;
Enter fullscreen mode Exit fullscreen mode

请注意:

  1. 我使用14.2%作为组件的宽度Day,因为一周/行只能有 7 天,100% / 7大约为14.2%
  2. 对于Day样式组件,我将检查 2 个道具,如果日期是今天,isToday则显示灰色边框,如果选择了,则显示灰色背景isSelected

使用 React Hooks 管理日期/月份/年份状态

日历必须显示当前日期、月份和年份。它们被视为组件的状态useState。因此,我们使用React Hook 来管理这些状态。它们的初始值默认从今天的日期生成(您也可以将此默认值设置date为组件的 prop,以便进一步扩展)。

除了当前日期、月份和年份,你还需要startDay确定每个月的第一天是星期几(星期一、星期二或其他)。知道了日期后,你就能更轻松地识别日历中所有日期的位置。

创建所有状态后,我们还需要管理它们的更新。我们应该将date变量作为计算 、 和 的入口点day因此,我们可以使用React Hook 来更新month依赖,这样以后,当我们点击日历中的任意一天时,我们就可以调用来更新,并触发其余状态也进行更新。yearstartDayuseEffectdaymonthyearstartDaydatesetDate()date

const today = new Date();
const [date, setDate] = useState(today);
const [day, setDay] = useState(date.getDate());
const [month, setMonth] = useState(date.getMonth());
const [year, setYear] = useState(date.getFullYear());
const [startDay, setStartDay] = useState(calculateStartDayOfMonth(date));

useEffect(() => {
  setDay(date.getDate());
  setMonth(date.getMonth());
  setYear(date.getFullYear());
  setStartDay(calculateStartDayOfMonth(date));
}, [date]);
Enter fullscreen mode Exit fullscreen mode

获取每月的开始日期

如上所述,我们需要获取月份的开始日期,这相当简单直接。

function getStartDayOfMonth(date: Date) {
  return new Date(date.getFullYear(), date.getMonth(), 1).getDay();
}
Enter fullscreen mode Exit fullscreen mode

检查是否是闰年

我们还需要检查当前是否处于闰年,以便我们能够显示二月份的正确天数。

我从维基百科中提取了一张图片,以便更好地说明闰年的确定。

闰年

很明显,如果某一年是闰年,那么该年份可以被 4 和 400 整除,但不能被 100 整除。

例如,

  • 2020 年是闰年,因为它可以被 4 整除
  • 2010 年不是闰年,因为它不能被 4 整除
  • 2000 年是闰年,因为它能被 400 整除
  • 1900 年不是闰年。虽然 1900 可以被 4 整除,但也可以被 100 整除

(最好为其编写一个单元测试!!)

function isLeapYear(year: number) {
  return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
Enter fullscreen mode Exit fullscreen mode

使用 TSX 构建日历!

最后,我们可以通过添加渲染部分来完成组件。

标题栏中的两个按钮setDate()在点击时触发。点击后会触发useEffect()回调,并更新daymonth,其中year显示在标题栏的标题中,用于判断该日期是否为当前选定的日期,并计算起始日期在本月 1 日之前应有多少个空块。startDaymonthdayday

const days = isLeapYear(date.getFullYear()) ? DAYS_LEAP : DAYS;

return (
  <Frame>
    <Header>
      <Button onClick={() => setDate(new Date(year, month - 1, day))}>Prev</Button>
      <div>
        {MONTHS[month]} {year}
      </div>
      <Button onClick={() => setDate(new Date(year, month + 1, day))}>Next</Button>
    </Header>
    <Body>
      {DAYS_OF_THE_WEEK.map(d => (
        <Day key={d}>
          <strong>{d}</strong>
        </Day>
      ))}
      {Array(days[month] + (startDay - 1))
        .fill(null)
        .map((_, index) => {
          const d = index - (startDay - 2);
          return (
            <Day
              key={index}
              isToday={d === today.getDate()}
              isSelected={d === day}
              onClick={() => setDate(new Date(year, month, d))}
            >
              {d > 0 ? d : ''}
            </Day>
          );
        })}
    </Body>
  </Frame>
);
Enter fullscreen mode Exit fullscreen mode

结论

今天我分享了如何用样式化的组件创建一个简单的 React 日历。它并没有想象的那么难,因为我认为唯一关键的部分就是确定一周的第一天是星期几。如果你能做到这一点,你就能确定所有日期的位置。剩下的工作只是修饰你的组件,让它更具吸引力。

谢谢阅读!!

特色图片由 Pexels 的 Bich Tran提供

鏂囩珷鏉ユ簮锛�https://dev.to/zhiyueyi/how-to-create-a-simple-react-calendar-with-styled-component-42n6
NEXT
React 和 Express 的 NPM 备忘单