前言
今天是学习react的第四天啦,我的第一个小目标是将平时使用的todo清单软件通过react在web端一比一的实现所有功能!
前面写的文章都会放在前言中:
📦代码仓库链接 react-todo gitee仓库
💻在线预览效果 react-todo 开发进度
# 👀从零开始学React第一天~React基础框架的构建(Create React App+Tailwind css+Material ui)
开发任务
今天的目标是实现一个🗓🗓日历组件,并且这个组件需要与昨天那个的时间选择器组件进行一个联动操作,如下图所示。
需求分析
日历在点击日期选择栏右侧详细日期时会弹出弹窗显示日历组件
日历组件不管哪个月份都有 七行六列共42格,列是以星期为区分的
日历顶部单箭头为上下周切换,双箭头为去明年切换
日历中,日期选中时为 主题色背景+白色字体 ,未选中时为黑色字体,非本月日期为灰色字体,当天日期为 主题色字体
不论我们点击顶部的日期选择栏或是日历中的日期,都会进行一个日期的选中操作,如果在日历中选中的日期超出了顶部日期选择栏的范围需要重新生成对应选中日期的那个星期
开发
需求分析完毕我们就开始开发啦。
弹窗组件
首先我们需要实现一个弹窗组件,这个组件比较好实现,直接通过mui中的Popover组件实现,在这个组件中,我为它传入了四个参数,anchorEl
用于控制弹窗组件的弹出,activeDate
用于传递给日历组件显示选中日期。
// dayTodo/datePopover.jsx
import React from "react"
import PropTypes from "prop-types"
import Popover from "@mui/material/Popover"
import Calendar from "../../components/Calendar"
DatePopover.propTypes = {
anchorEl: PropTypes.object,
setAnchorEl: PropTypes.func,
activeDate: PropTypes.object,
setDate: PropTypes.func,
}
export default function DatePopover(props) {
console.log("DatePopover渲染")
const { anchorEl, setAnchorEl, activeDate, setDate } = props
const handleClose = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
const id = open ? "simple-popover" : undefined
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<div className="p-3">
<Calendar activeDate={activeDate} setDate={setDate}></Calendar>
</div>
</Popover>
</div>
)
}
在日期选择栏组件中,我们引入一下组件,并且定义相关的hook,当点击按钮时获取id为date-picker
的节点并展示在这个节点的底下核心代码如下:
// dayTodo/dadtePicker.jsx
const [anchorEl, setAnchorEl] = useState(null)
const showDatePopover = () => {
const datePickerTarget = document.getElementById("date-picker")
setAnchorEl(datePickerTarget)
}
{/* 当前选中日期对应星期几 */}
<Button variant="text" onClick={showDatePopover}>
{`${activeDate.month() + 1}月${activeDate.date()}日 ${
activeDate.isToday()
? "今天"
: activeDate.localeData().weekdays(dayjs(activeDate))
}`}
</Button>
<DatePopover
activeDate={activeDate}
setDate={setDate}
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
></DatePopover>
这样我们的弹窗就实现完成了,接下来进入日历组件的开发。
日历组件
我将日历组件放在了公共组件compones文件夹中,以便之后复用。
日历组件不管哪个月份都有 七行六列共42格,列是以星期为区分的
根据需求我们需要先生成日历的数据,我的思路是先获取当前选中日期那个月的第一天,在通过当月第一天获取那一周的对应周一
这里是实现日历数据的代码:
const { activeDate, setDate } = props
const [first, setFirstDate] = useState(activeDate.date(1))
const firstNumber = first.day()
? first.day(1)
: dayjs(first).subtract(1, "day").day(1)
const dateArr = Array.from({ length: 42 }).map((item, index) => {
return dayjs(firstNumber).add(index, "day")
})
这里有一点需要注意,我们要判断选中日期当月第一天是否为周日,因为在day.js中,一周是从周日开始的,如果不判断部分月份就会出现一下情况,失去了一号的日期。如下图:
下面实现这个需求:
日历顶部单箭头为上下周切换,双箭头为去明年切换
实现的代码如下:
先定义方法,通过hook操作修改选中日期对应的月份或年份,然后重新渲染组件
const [first, setFirstDate] = useState(activeDate.date(1))
const toOtherMonth = (to) => {
// 跳转到其他月份
const lastMonthFirstDate = first.month(first.month() + to).date(1)
setFirstDate(lastMonthFirstDate)
}
const toOtherYear = (to) => {
// 跳转到其他月份
const lastYearFirstDate = first.year(first.year() + to).date(1)
setFirstDate(lastYearFirstDate)
}
在标签中调用方法:
<IconButton
onClick={() => {
toOtherYear(-1)
}}
>
<KeyboardDoubleArrowLeftIcon fontSize="small" />
</IconButton>
<IconButton onClick={() => toOtherMonth(-1)}>
<ChevronLeftIcon fontSize="small" />
</IconButton>
<div className="text-sm">
{first.year()}年 {first.month() + 1}月
</div>
<IconButton onClick={() => toOtherMonth(1)}>
<ChevronRightIcon fontSize="small" />
</IconButton>
<IconButton
onClick={() => {
toOtherYear(1)
}}
>
<KeyboardDoubleArrowRightIcon fontSize="small" />
</IconButton>
日历中,日期选中时为 主题色背景+白色字体 ,未选中时为黑色字体,非本月日期为灰色字体,当天日期为 主题色字体
这个需求的话我们可以通过行内样式来判断,但是如果写行内样式的话可读性太差了,所以我封装成了一个函数:
const dateClass = (date) => {
if (date.isSame(activeDate, "day")) {
return "bg-primary"
} else {
if (date.isToday()) {
return "text-primary font-bold"
}
if (date.month() !== first.month()) {
return "text-gray-300"
}
}
}
在标签中使用一下:
{dateArr.map((item, index) => {
return (
<div
className={`h-10 w-10 flex items-center justify-center cursor-pointer ${dateClass(
item
)} `}
key={index}
onClick={() => {
setDate(item)
}}
>
{item.date()}
</div>
)
})}
不论我们点击顶部的日期选择栏或是日历中的日期,都会进行一个日期的选中操作,如果在日历中选中的日期超出了顶部日期选择栏的范围需要重新生成对应选中日期的那个星期
最后我们要实现一个日历组件与顶部日期选择栏组件的一个联动,这一点我们就需要从顶部日期选择栏组件下手了,核心代码如下
// dayTodo/DatePicker.jsx
const [activeDate, setDate] = useState(dayjs())
useEffect(() => {
setWeek(getThisWeek())
}, [activeDate])
// 本周七天的日期对象数组
const getThisWeek = () => {
return Array.from({ length: 7 }).map((item, index) => {
return activeDate.isoWeekday(index + 1)
})
}
const [thisWeek, setWeek] = useState(getThisWeek())
这里我使用了一个新hookuseEffect
,这是我第一次使用,效果类似vue中的 watch
,用于监听变量变化,然后执行回调函数,这里我通过这个hook判断如果选中日期改变就重新生成一个新的日期数组,以随着日期的改变而改变。
总结
其实第四天的之前就写完了,结果给我误操作更新成了空文件😭。肝了超级久才重新写了一次,之前这篇文章长达三千字呢。
这几天又看了一下react的文档,了解了更多api,原来react hook和vue3 composition api的相似之处还是很多的,预计后面出一篇这个项目简单的性能优化的一些东西,后面再上redux啦。
最后我花了点时间用腾讯云托管将项目部署了一下,方便大家查看进度 ,点下面链接就能看到噢。如果大家感兴趣如何部署的话我之后也可以单独出一篇讲讲。希望大家多多点赞支持~
💻在线预览效果 react-todo 开发进度