怎么用React撸一个日程组件(react,移动开发)

时间:2024-05-01 19:25:06 作者 : 石家庄SEO 分类 : 移动开发
  • TAG :

目录结构

└─Calendar
│ data.d.ts 类型定义文件
│ index.tsx 入口文件

├─components
│ ├─CalendatrHeader 头部容器组件
│ │ │ index.less
│ │ │ index.tsx
│ │ │
│ │ └─components
│ │ ├─DailyOptions 顶部切换日期和切换模式状态组件
│ │ │ index.less
│ │ │ index.tsx
│ │ │
│ │ └─WeeklyOptions 周模式日期和星期组件
│ │ index.less
│ │ index.tsx
│ │
│ ├─Container 容器组件
│ │ Container.tsx
│ │ index.less
│ │
│ ├─ScheduleCantainer 下半部日程容器
│ │ index.less
│ │ index.tsx
│ │
│ └─ScheduleItem 灰色部分每一条日程组件
│ index.less
│ index.tsx

└─utils
index.ts 工具文件

?拆分组件

仔细看图, 不难看出, 组件大块上我拆分成了三个部分:
Container容器: 该组件是整个组件的容器, 负责UI核心状态数据, 维护两个状态:

  1. targetDay: 当前选中日期时间戳(为什么选用时间戳后续解释);

  2. switchWeekAndDay: 保存日和周的状态;

CalendatrHeader头部容器组件: Container容器的子组件, 该组件负责切换日期, 改变组件周和日的状态; 该组件内, 包含日历组件, 星期组件, 日期筛选组件, 日和周切换组件, 今天按钮组件, 最后还有一个业务组件的容器(businessRender);

ScheduleCantainer日程容器组件: 该组件被25 (因为是从今天0点到次日凌晨0点的区间) 个scheduleRender组件撑开, 子组件还包括时间刻度组件;

scheduleRender: 特意说一下这个组件, 这个组件接受一个回调, 回调会返回一个JSX, 这个JSX就是调用者传入的自定义样式的日程组件(具体内容在后文讲吧);

这就是大致的组件拆分, 文字表达确实欠佳, 可以结合图片YY;

接下来就开干吧!!!

代码实现

先看一下接受的参数类型定义:

typedataType={startTime:DOMTimeStamp;//开始时间戳endTime:DOMTimeStamp;//结束时间戳[propsName:string]:any;//业务数据};typeContainerType={data:dataType[];//业务数据initDay?:DOMTimeStamp;//初始化时间戳onChange?:(params:DOMTimeStamp)=>void;//改变日期时的onChange方法height?:number;//ScheduleCantainer容器的高度scheduleRender?:({data:dataType,timestampRange:[DOMTimeStamp,DOMTimeStamp],})=>JSX.Element;//传入的回调,会接收到当前这条数据的业务数据,当前业务数据所在的时间戳范围;businessRender?:({timestamp:DOMTimeStamp})=>React.ReactNode;//传入的业务组件,查询前端蔡徐坤那个,看图,想起来了吗?mode?:"day"|"week";//初始化展示日和天的模式};

Container容器组件

代码:

constContainer:React.FC<ContainerType>=({initDay,onChange,scheduleRender,businessRender,data,height=560,mode="day",})=>{//当前选择日期时间戳const[targetDay,setTargetDay]=useState<DOMTimeStamp>(initDay);//切换日和周const[switchWeekandDay,setSwitchWeekandDay]=useState<"day"|"week">(mode);return(<divclassName={style.Calendar_Container}><CalendatrHeadertargetDay={targetDay}setTargetDay={(timestamp)=>{onChange(timestamp);setTargetDay(timestamp);}}businessRender={businessRender}switchWeekandDay={switchWeekandDay}setSwitchWeekandDay={setSwitchWeekandDay}/><ScheduleCantainerheight={height}data={data}targetDay={targetDay}scheduleRender={scheduleRender}/></div>);};

看代码可以思考一下, 肯定要将全局的状态数据提升到最高层级去控制, 也符合React的组件设计哲学;

维护了当前时间戳和日/周的状态, 所有子组件的状态都是根据targetDay去展示的;

CalendatrHeader头部容器组件

头部容器我觉得其他的还好, 由于星期是写死的(主要是参考了一下苹果的那个日程组件, 苹果的星期就没换, 所以参考大厂优秀的设计), 所以比较敲脑壳的就是如何能准确的展示一周的日期;

其实展示一周的日期我写了两种方式:

第一种是以当前的日期的星期为基准, 分别向前和向后去计算, 最后输出一个[29, 30, 31, 1, 2, 3, 4]这样的List, 如果恰巧今天是1号 或者 2号, 就去拉去上个月最后一天的日期往前递减;

第二种方式就是下面代码的方式, 也是拿到当前日期的星期去定位, 通过时间戳去动态计算出来, 只要知道往前减几天, 往后追加几天就好了;

其实两种方式都可以, 最后我用了第二种, 显然第二种更加简洁;

如下图:

怎么用React撸一个日程组件

当前一周就要输出[12, 13, 14, 15, 16, 17, 18]

下面是上述难点具体实现的代码:

constcalcWeekDayList:(params:number)=>WeekType=(params)=>{constresult=[];for(leti=1;i<weekDay(params);i++){result.unshift(params-3600*1000*24*i);}for(leti=0;i<7-weekDay(params)+1;i++){result.push(params+3600*1000*24*i);}return[...result]asWeekType;};

代码:

constCalendatrHeader:React.FC<CalendatrHeaderType>=({targetDay,setTargetDay,switchWeekandDay,businessRender,setSwitchWeekandDay,})=>{//当前一周的日期const[dateTextList,setDateTextList]=useState<WeekType|[]>([]);//这个状态是在切换周的时候,直接增加或者减少一周的时间戳,下一周或者上一周的日期就会被自动算出来;const[currTime,setCurrTime]=useState<number>(targetDay);useEffect(()=>{setDateTextList(calcWeekDayList(targetDay));},[targetDay]);//根据当前时间戳,计算之前和之后天数的日期,由于星期是固定不变的,所以只计算当前一周的日期就好了constcalcWeekDayList:(params:number)=>WeekType=(params)=>{constresult=[];for(leti=1;i<weekDay(params);i++){result.unshift(params-3600*1000*24*i);}for(leti=0;i<7-weekDay(params)+1;i++){result.push(params+3600*1000*24*i);}return[...result]asWeekType;};constonChangeWeek:(type:"prevWeek"|"nextWeek",switchWay:"week"|"day")=>void=(type,switchWay,)=>{if(switchWay==="week"){constcalcWeekTime=type==="prevWeek"?currTime-3600*1000*24*7:currTime+3600*1000*24*7;setCurrTime(calcWeekTime);setDateTextList([...calcWeekDayList(calcWeekTime)]);}if(switchWay==="day"){constcalcWeekTime=type==="prevWeek"?targetDay-3600*1000*24:targetDay+3600*1000*24;setCurrTime(calcWeekTime);setTargetDay(calcWeekTime);}};return(<divclassName={style.Calendar_Header}><DailyOptionstargetDay={targetDay}setCurrTime={setCurrTime}setTargetDay={setTargetDay}dateTextList={dateTextList}switchWeekandDay={switchWeekandDay}setSwitchWeekandDay={(value)=>{setSwitchWeekandDay(value);if(value==="week"){setDateTextList(calcWeekDayList(targetDay));}}}onChangeWeek={(type)=>onChangeWeek(type,switchWeekandDay)}/>{switchWeekandDay==="week"&&(<WeeklyOptionstargetDay={targetDay}setTargetDay={setTargetDay}dateTextList={dateTextList}/>)}<divclassName={style.Calendar_Header_businessRender}><divclassName={style.Calendar_Header_Zone}>GMT+8</div>{businessRender({timestamp:targetDay})}</div></div>);};

DailyOptions : 其实就是头部切换"一周日期" & "日和周模式" & "今天"的组件的容器;

WeeklyOptions : 这个是下面展示当前一周星期几和日期的组件, 如果切换为day的话不展示

businessRender: 这个就是肖战哥哥那一栏用户传入的业务组件;

ScheduleCantainer详细日程容器

左侧刻度

左侧刻度其实就是写死的 从00:00 - 01:00 ---> 23:00 - 00:00, 但是在写的时候有一个小的问题, 就是这个组件是浮动到左侧的, 而且他要随着右侧条目的滚动而滚动, 其实一开始我写到一个盒子里了, 滚动容器整体就一起滚动了, 但是遇到了一个小问题, 由于右侧条目会变得超宽, 就会出现横向滚动条, 如果横滚整个容器的话, 左侧的时间刻度就会被滚动出可视区域.

所以还是绝对定位之后, 监听右侧日程条目的滚动事件, 动态的改变左侧的style的top值, 反向赋值就好了, 由于是向下滚动的, 所以左侧的时间刻度需要向上滚动, 所以top值取反就会达到同步的效果; 真是个小机灵鬼吧, 嘿嘿; 这个代码就不占用篇幅了, 大家自由发挥吧, 如果有更好的方式, 欢迎评论区留言.

ScheduleItem日程容器条目

先看下这个组件的代码:

constScheduleItem:React.FC<ScheduleItemType>=({timestampRange,dataItem,scheduleRender,width,dataItemLength,})=>{//计算容器高度constcalcHeight:(timestampList:[number,number])=>number=(timestampList)=>timestampList.length>1?(timestampList[1]-timestampList[0])/1000/60/2:30;constcalcTop:(startTime:number)=>number=(startTime)=>moment(startTime).minute()/2;//计算ScheduleItem宽度constcalcWidth:(w:number,d:number)=>string=(w,d)=>width===0||dataItemLength*width<347?"100%":`${d*w}px`;return(<divstyle={{position:"relative"}}className={style.Calendar_ScheduleItem_Fath}><divclassName={style.Calendar_ScheduleItem}style={{width:calcWidth(width,dataItemLength)}}>{dataItem.map((data,index)=>{return(<Fragmentkey={index}>{data.startTime>=timestampRange[0]&&data.startTime<timestampRange[1]&&(<divclassName={`${style.Calendar_ScheduleItem_container}Calendar_ScheduleItem_container`}style={{height:`${calcHeight([data.startTime,data.endTime])||30}px`,top:calcTop(data.startTime),}}>{scheduleRender({data,timestampRange})}</div>)}</Fragment>);})}</div></div>);};

这一部分呢(就是下面灰色一条一条的部分), 为什么要单独出一个组件呢? 可以先思考一下......

好了, 不卖关子了, 其实就是为了好定位用户的日程数据, 例如今天的10:00 -- 11:00, 定位到哪里的问题.

还记得这个API吗?

scheduleRender?:({data:dataType,timestampRange:[DOMTimeStamp,DOMTimeStamp],})=>JSX.Element;

这个组件内会有[DOMTimeStamp, DOMTimeStamp] 这样的一个参数(DOMTimeStamp时间戳的意思), 这两个时间戳其实就是当前时段的 10:00 -- 11:00 的其实和截至时间戳, 由于我们接受的startTime和endTime也是时间戳, 通过比较大小是否在这个范围, 就可以控制展示和隐藏, 这回明白为什么采用时间戳了吧, 直接比较数字大小就好了;

我们再说一下这个东东的样式问题:

其实这个东东我我写死了30px, 原因呢就是因为一小时是60分钟, 如果60px的话太高了, 所以写了30px, 方便定位嘛, 毕竟我懒, 不想太复杂的计算;

所以定位计算也就一行代码: const calcTop: (startTime: number) => number = (startTime) => moment(startTime).minute() / 2; 高度定位问题结了! 哈哈~~

接下来呢, 还有一个问题就是高度问题, 如图:

怎么用React撸一个日程组件

高度计算其实也不难, 主要根据当前起止时间的区间范围去计算( 1px 两分钟 ), 具体实现看代码:

constcalcHeight:(timestampList:[number,number])=>number=(timestampList)=>timestampList.length>1?(timestampList[1]-timestampList[0])/1000/60/2:30;

首先会判断入参的时间戳是不是只有一个时间, 如果只有开始时间, 没有结束时间, 写死30px, 如果有起止时间, 就去转成分钟动态计算一下;

最后还有一个问题, 业务数据是怎么传进去是如何渲染到组件的:

先看一下我们传入data字段的JSON:

[{startTime:1626057075000,//开始时间endTime:1626070875000,//结束时间value:"any",//业务数据},{startTime:1626057075000,endTime:1626070875000,value:"any",},{startTime:1626057075000,endTime:1626070875000,value:"any",},{startTime:1626057075000,endTime:1626070875000,value:"any",},];

其实我们在循环渲染这个ScheduleItem组件的时候, 用那个写死的24h的list去循环, 之后, 循环的时候, 动态的去业务数据中去查找符合当次循环的时间范围内的业务数据, 把这个数据塞到组件内; 大致代码如下:

for(leti=0;i<HoursList.length;i++){resule.push({timestampRange:[todayTime+i*3600*1000,todayTime+(i+1)*3600*1000],dataItem:[//由于当前一个时间段,日程可能冲突,所以要有一个list传入组件...data.filter((item)=>{return(item.startTime>=todayTime+i*3600*1000&&item.startTime<todayTime+(i+1)*3600*1000);}),],});}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:怎么用React撸一个日程组件的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:React中Portals与错误边界处理怎么实现下一篇:

8 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18