您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么用React擼一個日程組件”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么用React擼一個日程組件”吧!
└─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核心狀態數據, 維護兩個狀態:
targetDay: 當前選中日期時間戳(為什么選用時間戳后續解釋);
switchWeekAndDay: 保存日和周的狀態;
CalendatrHeader頭部容器組件: Container容器的子組件, 該組件負責切換日期, 改變組件周和日的狀態; 該組件內, 包含日歷組件, 星期組件, 日期篩選組件, 日和周切換組件, 今天按鈕組件, 最后還有一個業務組件的容器(businessRender);
ScheduleCantainer日程容器組件: 該組件被25 (因為是從今天0點到次日凌晨0點的區間) 個scheduleRender組件撐開, 子組件還包括時間刻度組件;
scheduleRender: 特意說一下這個組件, 這個組件接受一個回調, 回調會返回一個JSX, 這個JSX就是調用者傳入的自定義樣式的日程組件(具體內容在后文講吧);
這就是大致的組件拆分, 文字表達確實欠佳, 可以結合圖片YY;
接下來就開干吧!!!
先看一下接受的參數類型定義:
type dataType = { startTime: DOMTimeStamp; // 開始時間戳 endTime: DOMTimeStamp; // 結束時間戳 [propsName: string]: any; // 業務數據 }; type ContainerType = { 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"; // 初始化展示日和天的模式 };
代碼:
const Container: 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 ( <div className={style.Calendar_Container}> <CalendatrHeader targetDay={targetDay} setTargetDay={(timestamp) => { onChange(timestamp); setTargetDay(timestamp); }} businessRender={businessRender} switchWeekandDay={switchWeekandDay} setSwitchWeekandDay={setSwitchWeekandDay} /> <ScheduleCantainer height={height} data={data} targetDay={targetDay} scheduleRender={scheduleRender} /> </div> ); };
看代碼可以思考一下, 肯定要將全局的狀態數據提升到最高層級去控制, 也符合React的組件設計哲學;
維護了當前時間戳和日/周的狀態, 所有子組件的狀態都是根據targetDay去展示的;
頭部容器我覺得其他的還好, 由于星期是寫死的(主要是參考了一下蘋果的那個日程組件, 蘋果的星期就沒換, 所以參考大廠優秀的設計), 所以比較敲腦殼的就是如何能準確的展示一周的日期;
其實展示一周的日期我寫了兩種方式:
第一種是以當前的日期的星期為基準, 分別向前和向后去計算, 最后輸出一個[29, 30, 31, 1, 2, 3, 4]這樣的List, 如果恰巧今天是1號 或者 2號, 就去拉去上個月最后一天的日期往前遞減;
第二種方式就是下面代碼的方式, 也是拿到當前日期的星期去定位, 通過時間戳去動態計算出來, 只要知道往前減幾天, 往后追加幾天就好了;
其實兩種方式都可以, 最后我用了第二種, 顯然第二種更加簡潔;
如下圖:
當前一周就要輸出[12, 13, 14, 15, 16, 17, 18]
下面是上述難點具體實現的代碼:
const calcWeekDayList: (params: number) => WeekType = (params) => { const result = []; for (let i = 1; i < weekDay(params); i++) { result.unshift(params - 3600 * 1000 * 24 * i); } for (let i = 0; i < 7 - weekDay(params) + 1; i++) { result.push(params + 3600 * 1000 * 24 * i); } return [...result] as WeekType; };
代碼:
const CalendatrHeader: React.FC<CalendatrHeaderType> = ({ targetDay, setTargetDay, switchWeekandDay, businessRender, setSwitchWeekandDay, }) => { // 當前一周的日期 const [dateTextList, setDateTextList] = useState<WeekType | []>([]); // 這個狀態是在切換周的時候, 直接增加或者減少一周的時間戳, 下一周或者上一周的日期就會被自動算出來; const [currTime, setCurrTime] = useState<number>(targetDay); useEffect(() => { setDateTextList(calcWeekDayList(targetDay)); }, [targetDay]); // 根據當前時間戳, 計算之前和之后天數的日期, 由于星期是固定不變的, 所以只計算當前一周的日期就好了 const calcWeekDayList: (params: number) => WeekType = (params) => { const result = []; for (let i = 1; i < weekDay(params); i++) { result.unshift(params - 3600 * 1000 * 24 * i); } for (let i = 0; i < 7 - weekDay(params) + 1; i++) { result.push(params + 3600 * 1000 * 24 * i); } return [...result] as WeekType; }; const onChangeWeek: (type: "prevWeek" | "nextWeek", switchWay: "week" | "day") => void = ( type, switchWay, ) => { if (switchWay === "week") { const calcWeekTime = type === "prevWeek" ? currTime - 3600 * 1000 * 24 * 7 : currTime + 3600 * 1000 * 24 * 7; setCurrTime(calcWeekTime); setDateTextList([...calcWeekDayList(calcWeekTime)]); } if (switchWay === "day") { const calcWeekTime = type === "prevWeek" ? targetDay - 3600 * 1000 * 24 : targetDay + 3600 * 1000 * 24; setCurrTime(calcWeekTime); setTargetDay(calcWeekTime); } }; return ( <div className={style.Calendar_Header}> <DailyOptions targetDay={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" && ( <WeeklyOptions targetDay={targetDay} setTargetDay={setTargetDay} dateTextList={dateTextList} /> )} <div className={style.Calendar_Header_businessRender}> <div className={style.Calendar_Header_Zone}>GMT+8</div> {businessRender({ timestamp: targetDay })} </div> </div> ); };
DailyOptions : 其實就是頭部切換"一周日期" & "日和周模式" & "今天"的組件的容器;
WeeklyOptions : 這個是下面展示當前一周星期幾和日期的組件, 如果切換為day的話不展示
businessRender: 這個就是肖戰哥哥那一欄用戶傳入的業務組件;
左側刻度其實就是寫死的 從00:00 - 01:00 ---> 23:00 - 00:00, 但是在寫的時候有一個小的問題, 就是這個組件是浮動到左側的, 而且他要隨著右側條目的滾動而滾動, 其實一開始我寫到一個盒子里了, 滾動容器整體就一起滾動了, 但是遇到了一個小問題, 由于右側條目會變得超寬, 就會出現橫向滾動條, 如果橫滾整個容器的話, 左側的時間刻度就會被滾動出可視區域.
所以還是絕對定位之后, 監聽右側日程條目的滾動事件, 動態的改變左側的style的top值, 反向賦值就好了, 由于是向下滾動的, 所以左側的時間刻度需要向上滾動, 所以top值取反就會達到同步的效果; 真是個小機靈鬼吧, 嘿嘿; 這個代碼就不占用篇幅了, 大家自由發揮吧, 如果有更好的方式, 歡迎評論區留言.
先看下這個組件的代碼:
const ScheduleItem: React.FC<ScheduleItemType> = ({ timestampRange, dataItem, scheduleRender, width, dataItemLength, }) => { // 計算容器高度 const calcHeight: (timestampList: [number, number]) => number = (timestampList) => timestampList.length > 1 ? (timestampList[1] - timestampList[0]) / 1000 / 60 / 2 : 30; const calcTop: (startTime: number) => number = (startTime) => moment(startTime).minute() / 2; // 計算 ScheduleItem 寬度 const calcWidth: (w: number, d: number) => string = (w, d) => width === 0 || dataItemLength * width < 347 ? "100%" : `${d * w}px`; return ( <div style={{ position: "relative" }} className={style.Calendar_ScheduleItem_Fath}> <div className={style.Calendar_ScheduleItem} style={{ width: calcWidth(width, dataItemLength) }} > {dataItem.map((data, index) => { return ( <Fragment key={index}> {data.startTime >= timestampRange[0] && data.startTime < timestampRange[1] && ( <div className={`${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; 高度定位問題結了! 哈哈~~
接下來呢, 還有一個問題就是高度問題, 如圖:
高度計算其實也不難, 主要根據當前起止時間的區間范圍去計算( 1px 兩分鐘 ), 具體實現看代碼:
const calcHeight: (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 (let i = 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 ); }), ], }); }
到此,相信大家對“怎么用React擼一個日程組件”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。