前言
今天是学习react的第三天啦,我的第一个小目标是将平时使用的todo清单软件通过react在web端一比一的实现所有功能!
过往开发文章都在这里:
📦代码仓库链接 react-todo gitee仓库
💻在线预览效果 react-todo 开发进度
# 👀从零开始学React第一天~React基础框架的构建(Create React App+Tailwind css+Material ui)
开发任务
今天的任务是:
将昨天开发左侧菜单栏抽离成单独的组件,如下图
实现Day Todo功能模块中的一个顶部栏日期的功能,如下图
开发
在今天的开发开始前我根据官方文档又新增了一个 eslint plugin eslint-plugin-react-hooks
我们先执行命令安装一下
yarn add eslint-plugin-react-hooks --save-dev
然后在 .eslintrc.js
中声明一下插件,目前完整的配置如下
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
},
extends: ["eslint:recommended", "plugin:react/recommended"],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: "module",
},
plugins: ["react", "react-hooks"],
rules: {
semi: [2, "never"],
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn", // 检查 effect 的依赖
},
}
eslint-plugin-react-hooks
具体的规则如下:
只在 React 函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook。 你可以:
- ✅ 在 React 的函数组件中调用 Hook
- ✅ 在自定义 Hook 中调用其他 Hook
抽离LeftNav组件
我们在Layout目录下新建一个 LeftNav
组件,并将左侧栏的相关代码抽离出来.
// /layout/LeftNav.jsx
import React from "react"
import PropTypes from "prop-types"
import { Link } from "react-router-dom"
import { List, ListItem, ListItemButton, ListItemIcon } from "@mui/material"
import WbSunnyOutlinedIcon from "@mui/icons-material/WbSunnyOutlined"
import CalendarTodayOutlinedIcon from "@mui/icons-material/CalendarTodayOutlined"
import CalendarViewMonthOutlinedIcon from "@mui/icons-material/CalendarViewMonthOutlined"
import InboxOutlinedIcon from "@mui/icons-material/InboxOutlined"
import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined"
import ListAltOutlinedIcon from "@mui/icons-material/ListAltOutlined"
LeftNav.propTypes = {
active: PropTypes.string,
changeActive: PropTypes.func,
}
export default function LeftNav(props) {
const { active, changeActive } = props
const data = [
{ icon: <WbSunnyOutlinedIcon />, label: "Day Todo", path: "dayTodo" },
{
icon: <CalendarTodayOutlinedIcon />,
label: "最近待办",
path: "recentlyTodo",
},
{
icon: <CalendarViewMonthOutlinedIcon />,
label: "日期概览",
path: "dateOverview",
},
{ icon: <InboxOutlinedIcon />, label: "待办箱", path: "todyBox" },
{ icon: <SearchOutlinedIcon />, label: "搜索", path: "search" },
{ icon: <ListAltOutlinedIcon />, label: "数据复盘", path: "dataReview" },
]
return (
<List>
{data.map((item) => {
return (
<Link to={item.path} key={item.path}>
<ListItem
disablePadding
className={item.path === active ? "bg-gray-200" : ""}
onClick={() => changeActive(item.path)}
>
<ListItemButton>
<ListItemIcon>{item.icon}</ListItemIcon>
<span className="text-sm">{item.label}</span>
</ListItemButton>
</ListItem>
</Link>
)
})}
</List>
)
}
而原本的页面就变得更加清爽了,我将hook定义的 active
和setAcitve
这两个变量改为了在页面中定义,然后以组件参数的形式传入 LeftNav
。
由于使用了eslint还得声明一下组件的prop类型,根据官方文档的操作使用了 PropTypes
类型验证器。
下面是抽离组件后页面的代码:
// layout/index.jsx
import React, { useState } from "react"
import routes from "../routes"
import { Route, Routes } from "react-router-dom"
import { Button, Card } from "@mui/material"
import LeftNav from "./LeftNav"
export default function Layout() {
const [active, setActive] = useState("dayTodo")
return (
<div className="w-screen h-screen flex items-center justify-center">
<Card variant="outlined" className="w-2/3 h-3/4 shadow-lg bg-white flex">
<div className="w-1/5 bg-gray-50">
<div className=" flex items-center justify-center p-5">
<Button variant="contained">这是一个按钮</Button>
</div>
<LeftNav
active={active}
changeActive={(active) => setActive(active)}
/>
</div>
<div className=" w-4/5">
<Routes>
{routes.map((item) => {
return (
<Route
index={item.path === active}
key={item.path}
exact
path={item.path}
element={item.component()}
></Route>
)
})}
</Routes>
</div>
</Card>
</div>
)
}
实现顶部栏日期
我们先分析一下需求:
- 默认是显示本周的七个日期,并且今天的日期会亮起
- 右侧是具体的时间,如果我们选择其他日期,激活亮起的日期和右侧的具体时间也会随之改变。如果点击右侧具体时间会弹出一个完整日历
- 点击太阳图标可以回到今天的日期
- 点击左右分别代表切换到上周或者下周的日期 首先我们在DayTodo目录下新建一个 DatePicker 组件,用于将我们这个顶部的日期选择栏单独编写成一个组件,然后在页面中引用。
引入Day.js
经过上面的需求分析,我们需要获取的数据是 今天的日期 和 每周的七个日期 ,这种频繁涉及到时间处理的操作我会选择使用 Day.js 这个js库。
Day.js是一个极简的JavaScript库,可以为现代浏览器解析、验证、操作和显示日期和时间。 Day.js官方文档链接
首先我们安装一下这个库
yarn add dayjs
然后我们在DatePicker 组件引入,代码如下:
// dayTodo/DatePick.jsx
import React from 'react';
import dayjs from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek'
dayjs.extend(isoWeek)
export default function DatePicker(){
const today = dayjs().date()
const thisWeek = Array.from({length:7}).map((item,index)=>{
return dayjs().isoWeekday(index + 1).date()
})
console.log(today,thisWeek)
return (<div></div>)
}
在上面的代码中我引入了dayjs,然后定义了两个变量
today:今天的日期,例如26号
thisWeek:本周的七个日期组成的数组
我使用了 Array.from({length:7})
这样一个方法快速创建了长度为七的数组,再使用map
遍历,通过dayjs的isoWeek()
方法传入遍历的索引再通过 date()
方法获取日期就实现了功能
获取或设置ISO day of the week (opens new window),其中1为星期一,7为星期日。
我们将两个变量打印出来,看看是不是我们所需要的数据,控制台输出结果如下:
完善页面
然后我们接着完善页面和数据,最终完成的组件代码如下:
// dayTodo/DatePick.jsx
import React, { useState } from "react"
import { IconButton, Button } from "@mui/material"
import ArrowBackIosNewOutlinedIcon from "@mui/icons-material/ArrowBackIosNewOutlined"
import ArrowForwardIosOutlinedIcon from "@mui/icons-material/ArrowForwardIosOutlined"
import dayjs from "dayjs"
import isoWeek from "dayjs/plugin/isoWeek"
import isToday from "dayjs/plugin/isToday"
import localeData from "dayjs/plugin/localeData"
import "dayjs/locale/zh-cn"
dayjs.locale("zh-cn")
dayjs.extend(isoWeek)
dayjs.extend(isToday)
dayjs.extend(localeData)
export default function DatePicker() {
// 当前选中的日期
const [activeDate, setDate] = useState(dayjs())
// 判断是否为选择的日期
const isActive = (item) => item.date() === activeDate.date()
// 本周七天的日期对象数组
const thisWeek = Array.from({ length: 7 }).map((item, index) => {
return dayjs().isoWeekday(index + 1)
})
return (
<div className=" p-5">
<div className="inline-flex rounded-md bg-gray-100 p-1 items-center ">
{/* 上翻按钮 */}
<IconButton size="small">
<ArrowBackIosNewOutlinedIcon fontSize="12px" />
</IconButton>
{/* 七天日期 */}
{thisWeek.map((item) => {
return (
<div
className={`flex items-center justify-center cursor-pointer w-7 h-7 rounded-full mx-1 ${
item.date() === activeDate.date()
? "bg-primary"
: item.isToday()
? "bg-gray-200"
: "hover:bg-gray-200"
}`}
key={item}
size="small"
onClick={() => {
setDate(item)
}}
>
<span className="text-sm">
{item.isToday() ? "今" : item.date()}
</span>
</div>
)
})}
{/* 下翻按钮 */}
<IconButton size="small">
<ArrowForwardIosOutlinedIcon fontSize="12px" />
</IconButton>
{/* 当前选中日期对应星期几 */}
<Button variant="text">
{`${activeDate.month() + 1}月${activeDate.date()}日 ${
activeDate.isToday()
? "今天"
: activeDate.localeData().weekdays(dayjs(activeDate))
}`}
</Button>
</div>
</div>
)
}
实现的效果如下图:
最终实现这个代码还是花了不少时间的,主要都是在翻看Dayjs的文档,下面解析一下一些比较复杂的点:
样式方面主要的难点在于 判断当前选中的时间 和 判断选择的是否为今天,我分别使用的dayjs提供的方法 isToday 和自己定义的一个方法
显示当前选中的时间是中文的星期几使用的是dayjs的一个本地化的功能,先加载中文包,然后基于当前选中的时间来获取。
目前还剩下 上下周切换 和一个 回到今天的小太阳按钮 就完成了这个时间选择栏啦。
目前这个组件也是耦合了很多代码,事实上跟日期相关的数据在后面肯定是很多组件需要使用到的,并且dayjs引入的插件和语言也是应该抽离的。我想等到我引入一个全局状态管理库的时候再统一抽离出去。到时再研究一下redux如何使用和封装,基本上页面和临时的数据就可以顺利走通啦~
总结
今天写的组件使用到的样式比较多,事实上我原本想引入一个 css-in-js 的库来学习一下新思想的,但是看了文档后最终没有选择引入,因为还不是非常有必要。
在样式比较多的地方可以显著感觉到vue与react的不同。在写vue的时候总是担心 tailwindcss 提供的样式太长了,是否需要单独抽离到style中去写。
但是在react中直接没有顾虑,基本全部样式都耦合在jsx中了,目前还没有涉及到修改组件原生样式的功能,我还不知道如果react要写 style
的话怎么样才是比较优雅的方法,后面慢慢摸索一下~
今天在写代码的时候一直在怀疑这样一遍写文章一遍写代码是不是 耽误了学习的进度,况且文章也没多少人看,有必要这样坚持吗。
但睡了个午觉醒来还是打开了掘金(大概我还是喜欢那种能帮助到别人的感觉吧),衷心希望我的开发思路或者踩坑过程可以帮到大家!