手撸你的第一个组件「DatePicker」

1,972 阅读2分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

前言

本人准大三前端菜鸡一枚,本文记录我为开源库贡献组件pr以及第一次封装通用组件,文章里有什么不合理地方希望大家能够帮忙指出👂

成品展示

image.png

为什么想敲这样一个组件

说到为何有这个想法,那就得从一个平平无奇的凌晨说起,在掘金上逛到了文章React组件库Concis,寻求社区有兴趣的小伙伴加入...,对这很有兴趣的我在博主的GitHub上留下issue表达了我的想法,并在昨天贡献了我的第一个组件(DatePicker)。下面我就来说说我如何一步步写出相关代码(💩!!!)的。

构思需要的提供的API

  • className:自定义类名
  • format:日期格式化
  • showClear:是否显示清除按钮
  • align:组件出现方向(沿用已经完成的Poover组件进行包裹)
  • handleChange:选择点击事件时回调函数
  • disableCheck:是否禁用选项

开始设计

1.做出组件显示结构

Header部分

<div className="date-picker-select">
  <div className="left-select">
    <DoubleLeftOutlined onClick={() => setYear(nowDate.year - 1)} />
    <LeftOutlined onClick={() => setMonth(nowDate.month - 1, 'del')} />
  </div>
  <div className="middle-select">
    <span>{nowDate.year}</span>-<span>{nowDate.month}</span>
  </div>
  <div className="right-select">
    <RightOutlined onClick={() => setMonth(nowDate.month + 1, 'add')} />
    <DoubleRightOutlined onClick={() => setYear(nowDate.year + 1)} />
  </div>
</div>

Panel部分

样式采用Flex布局,子元素style使用flex:1,使子元素均分宽度

<table>
  <thead>
    <tr>
      {titleList.map((title, idx) => (
        <th key={idx}>{title}</th>
      ))}
    </tr>
  </thead>
  <tbody>
    {nowDayList.map((row, idx) => (
      <tr key={idx}>
        {row.map((day, idx) => (
          <td
            key={idx}
            onClick={() => setInputVal(day)}
            className={`${day.value === '' ? 'day-empty' : ''} ${
              day.disable ? 'disable' : ''
            } ${isSameDate(day.date) ? 'active' : ''}`}
          >
            {day.value}
          </td>
        ))}
      </tr>
    ))}
  </tbody>
</table>

2.功能代码设计

更新Panel面板

更新年月

随着用户在Header中更改年份与月份,需要更新对应月份的DatePanel面板,更新年份只需要简单对数据进行+-1操作,而月份有着1~12的显示,所以在更改之前需要做一些简单的判断。附上对应代码:

const setMonth = (month: number, type: string): void => {
  let date = {} as NowDateProps;
  if (type === 'add') {
    if (month > 12) {
      date = { ...nowDate, month: 1, year: nowDate.year + 1 };
    } else {
      date = { ...nowDate, month };
    }
  } else {
    if (month < 0) {
      date = { ...nowDate, month: 12, year: nowDate.year - 1 };
    } else {
      date = { ...nowDate, month };
    }
  }
  setNowDate(date);
};

更新日期面板

通过解构取得nowDate对象中的yearmonth值,我们此刻需要知道该月1号是星期几「firstWeekDay」以及该月的总天数「lastDay」,通过js的Date对象的内置方法可以解决前面的问题,在面板渲染中我使用了数组嵌套数组的方式渲染tr>td的嵌套结构,daysArr得到了长度为td个数的空数组(tr个数=td个数/7),对dayArr进行遍历,当index-firstWeekDay等于0时说明本次遍历应该填如该月份的1号。本次遍历还会储存Date对象(用于点击事件传值)对date进行disableCheck函数的验证,以供后续添加类名以及判断,最后使用lodash/chunk分割数组。贴上相关代码:

useEffect(() => {
  const { year, month } = nowDate;
  //1号是星期几
  const firstWeekDay = new Date(year, month, 1).getDay();
  //最后一天几号
  const lastDay = new Date(year, month, 0).getDate();
  const daysArr = new Array(Math.ceil((firstWeekDay + lastDay) / 7) * 7).fill('');
  setNowDayList(
    chunk(
      daysArr.map((_, i) => {
        const day = `${
          i - firstWeekDay <= -1 || i - firstWeekDay + 1 > lastDay ? '' : i - firstWeekDay + 1
        }`;
        const date = new Date(year, month - 1, Number(day));
        return {
          date,
          disable: disableCheck(date),
          value: day,
        };
      }),
      7,
    ),
  );
}, [nowDate.year, nowDate.month]);

active/disable类名

还记得得到日期面板时遍历中进行的disableCheck函数吗?disable类名就是由他决定是否添加上去的啦! 而active类名的添加是通过isSameDate函数进行判断年月日是否相同,返回值为布尔类型,默认的active类名添加在与今天相同的日期上,后续用户通过点击或赋值更改类名归属。贴上相关代码:

const [clickDate, setClickDate] = useState(new Date());
const isSameDate = (date: Date) => {
  return (
    date.getFullYear() === clickDate.getFullYear() &&
    date.getMonth() + 1 === clickDate.getMonth() + 1 &&
    date.getDate() === clickDate.getDate()
  );
};

3.table样式代码

直接贴代码:

table {
  width: 100%;
  padding: 10px;
  //border-top: 1px solid #e0e0e0;
  tr {
    display: flex;
    td,
    th {
      display: flex;
      flex: 1;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      width: 32px;
      height: 32px;
      margin: 5px 0;
      border-radius: 50%;
      &:hover:not(th):not(.day-empty, .disable, .active) {
        background-image: linear-gradient(#e0e0e0, #e0e0e0);
        background-position: center;
        cursor: pointer;
      }
    }
    .disable:not(.day-empty) {
      color: #a8abb2;
      background-color: #f5f7fa;
      border-radius: 0;
      cursor: not-allowed;
      opacity: 1;
    }
    .active {
      color: #fff0ec;
      background-image: linear-gradient(@concis-primary-color, @concis-primary-color);
    }
  }
}

总结

这样一个简单的DatePicker组件就完成啦,如果代码中有什么问题希望大家能够提出来,如果这篇文章对你有帮助,不妨点个赞喔。

源码链接

源码链接

线上文档

如果你看到了这,不妨给给Concis点个star⭐再走啊!!! 对这个项目有兴趣的也欢迎一起来提pr!!!

系列链接
手撸你的第一个组件
手撸React组件「TimePicker」
手撸React组件「Upload」文件上传