国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

手把手帶你用React擼一個(gè)日程組件

發(fā)布時(shí)間:2021-08-17 12:16 來(lái)源: 閱讀:0 作者:賊煩字符串er 欄目: JavaScript 歡迎投稿:712375056

目錄

    業(yè)務(wù)背景

    先簡(jiǎn)單描述一下業(yè)務(wù)場(chǎng)景吧, 就是會(huì )調用用戶(hù)在企業(yè)微信或者釘釘這類(lèi)辦公軟件里面的日程信息, 在web端給安排日程的展示, 如果日程沖突, 就會(huì )展示沖突那天的日程, 讓安排人員合理安排時(shí)間日程, 避免沖突, 如圖;

    使用技術(shù)

    1. UI框架: React(Hook);
    2. 插件: moment(對于一個(gè)位居18線(xiàn)的懶B碼農必備插件, 要不然自己轉來(lái)轉去的太麻煩了);

    技術(shù)難點(diǎn)

    1. API設計;
    2. 組件拆分;
    3. UI和業(yè)務(wù)解耦;
    4. 開(kāi)箱即用;

    設計思路

    😱一臉懵逼苦

    開(kāi)發(fā)項目的時(shí)候, 組件庫用的是千篇一律的antd, 評審過(guò)后下意識的去antd找了一下有沒(méi)有可以開(kāi)箱即用的組件.

    不巧!!! 還真沒(méi)有這種周或者日篩選的組件, 百般惱火, 阿里寫(xiě)了那么多組件, 為啥偏偏漏了這個(gè)呢?

    于是轉戰萬(wàn)能的百度, 查有沒(méi)有相關(guān)的組件, 后來(lái)查到了fullcalendar這個(gè)組件, 但是后來(lái)連人家的文檔和demo都沒(méi)看, 毅然決然的決定自己寫(xiě)一個(gè)!

    綜合看來(lái)有幾個(gè)原因哈:

    1. 雖然人家的組件寫(xiě)得好, 但是很多業(yè)務(wù)是千變萬(wàn)化的, 不一定能滿(mǎn)足所有的業(yè)務(wù)需求;
    2. 其次, 新來(lái)的開(kāi)發(fā)人員在不熟悉這個(gè)組件的時(shí)候, 還需要去閱讀文檔, 增加了維護成本;
    3. 第三點(diǎn)嘛, 在有限的時(shí)間內挑戰一下自己;

    🙄開(kāi)始構思

    其實(shí)開(kāi)始構思的時(shí)候, 也想過(guò)去參考一下優(yōu)秀組件的API設計, 但是, 現在有些組件寫(xiě)的真的很難用, 而且不理解他為蛤蟆這么寫(xiě), 所以用自己站在一個(gè)使用者的角度思考了一下, 還是覺(jué)得按照自己作為一個(gè)十八流的底層最懶B的程序員的調用方式去設計-------開(kāi)箱即用.

    而且還有很重要的一點(diǎn)就是和業(yè)務(wù)解耦, 方便其他的項目直接用起來(lái)呀, 何樂(lè )而不為呢?

    于是自己開(kāi)始花了一上午青春, 結合自己的想法, 繪制了一個(gè)設計圖:

    這個(gè)就是用ProcessOn繪制的, 本人用的不多, 畫(huà)得不好哈, 見(jiàn)諒!

    🌲目錄結構

    └─Calendar
        │  data.d.ts  類(lèi)型定義文件
        │  index.tsx  入口文件
        │
        ├─components
        │  ├─CalendatrHeader 頭部容器組件
        │  │  │  index.less
        │  │  │  index.tsx
        │  │  │
        │  │  └─components
        │  │      ├─DailyOptions 頂部切換日期和切換模式狀態(tài)組件
        │  │      │      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 工具文件

    🛠拆分組件

    仔細看圖, 不難看出, 組件大塊上我拆分成了三個(gè)部分:
    Container容器: 該組件是整個(gè)組件的容器, 負責UI核心狀態(tài)數據, 維護兩個(gè)狀態(tài):

    1. targetDay: 當前選中日期時(shí)間戳(為什么選用時(shí)間戳后續解釋);
    2. switchWeekAndDay: 保存日和周的狀態(tài);

    CalendatrHeader頭部容器組件: Container容器的子組件, 該組件負責切換日期, 改變組件周和日的狀態(tài); 該組件內, 包含日歷組件, 星期組件, 日期篩選組件, 日和周切換組件, 今天按鈕組件, 最后還有一個(gè)業(yè)務(wù)組件的容器(businessRender);

    ScheduleCantainer日程容器組件: 該組件被25 (因為是從今天0點(diǎn)到次日凌晨0點(diǎn)的區間) 個(gè)scheduleRender組件撐開(kāi), 子組件還包括時(shí)間刻度組件;

    scheduleRender: 特意說(shuō)一下這個(gè)組件, 這個(gè)組件接受一個(gè)回調, 回調會(huì )返回一個(gè)JSX, 這個(gè)JSX就是調用者傳入的自定義樣式的日程組件(具體內容在后文講吧);

    這就是大致的組件拆分, 文字表達確實(shí)欠佳, 可以結合圖片YY;

    接下來(lái)就開(kāi)干吧!!!

    代碼實(shí)現

    先看一下接受的參數類(lèi)型定義:

    type dataType = {
      startTime: DOMTimeStamp; // 開(kāi)始時(shí)間戳
      endTime: DOMTimeStamp; // 結束時(shí)間戳
      [propsName: string]: any; // 業(yè)務(wù)數據
    };
    
    type ContainerType = {
      data: dataType[]; // 業(yè)務(wù)數據
      initDay?: DOMTimeStamp; // 初始化時(shí)間戳
      onChange?: (params: DOMTimeStamp) => void; // 改變日期時(shí)的onChange方法
      height?: number; // ScheduleCantainer容器的高度
      scheduleRender?: ({
        data: dataType,
        timestampRange: [DOMTimeStamp, DOMTimeStamp],
      }) => JSX.Element; // 傳入的回調, 會(huì )接收到當前這條數據的業(yè)務(wù)數據, 當前業(yè)務(wù)數據所在的時(shí)間戳范圍;
      businessRender?: ({ timestamp: DOMTimeStamp }) => React.ReactNode; // 傳入的業(yè)務(wù)組件, 查詢(xún)前端蔡徐坤那個(gè), 看圖, 想起來(lái)了嗎?
      mode?: 'day' | 'week'; // 初始化展示日和天的模式
    };
    

    Container容器組件

    代碼:

    const Container: React.FC<ContainerType> = ({
      initDay,
      onChange,
      scheduleRender,
      businessRender,
      data,
      height = 560,
      mode = 'day',
    }) => {
      // 當前選擇日期時(shí)間戳
      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>
      );
    };
    

    看代碼可以思考一下, 肯定要將全局的狀態(tài)數據提升到最高層級去控制, 也符合React的組件設計哲學(xué);

    維護了當前時(shí)間戳和日/周的狀態(tài), 所有子組件的狀態(tài)都是根據targetDay去展示的;

    CalendatrHeader頭部容器組件

    頭部容器我覺(jué)得其他的還好, 由于星期是寫(xiě)死的(主要是參考了一下蘋(píng)果的那個(gè)日程組件, 蘋(píng)果的星期就沒(méi)換, 所以參考大廠(chǎng)優(yōu)秀的設計), 所以比較敲腦殼的就是如何能準確的展示一周的日期;

    其實(shí)展示一周的日期我寫(xiě)了兩種方式:

    第一種是以當前的日期的星期為基準, 分別向前和向后去計算, 最后輸出一個(gè)[29, 30, 31, 1, 2, 3, 4]這樣的List, 如果恰巧今天是1號 或者 2號, 就去拉去上個(gè)月最后一天的日期往前遞減;

    第二種方式就是下面代碼的方式, 也是拿到當前日期的星期去定位, 通過(guò)時(shí)間戳去動(dòng)態(tài)計算出來(lái), 只要知道往前減幾天, 往后追加幾天就好了;

    其實(shí)兩種方式都可以, 最后我用了第二種, 顯然第二種更加簡(jiǎn)潔;

    如下圖:

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

    下面是上述難點(diǎn)具體實(shí)現的代碼:

    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 | []>([]);
      // 這個(gè)狀態(tài)是在切換周的時(shí)候, 直接增加或者減少一周的時(shí)間戳, 下一周或者上一周的日期就會(huì )被自動(dòng)算出來(lái);
      const [currTime, setCurrTime] = useState<number>(targetDay); 
    
      useEffect(() => {
        setDateTextList(calcWeekDayList(targetDay));
      }, [targetDay]);
    
      // 根據當前時(shí)間戳, 計算之前和之后天數的日期, 由于星期是固定不變的, 所以只計算當前一周的日期就好了
      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 : 其實(shí)就是頭部切換"一周日期" & "日和周模式" & "今天"的組件的容器;

    WeeklyOptions : 這個(gè)是下面展示當前一周星期幾和日期的組件, 如果切換為day的話(huà)不展示: 如圖:

    businessRender: 這個(gè)就是肖戰哥哥那一欄用戶(hù)傳入的業(yè)務(wù)組件;

    ScheduleCantainer詳細日程容器

    也就是圖片這部分:

    其實(shí)這部分代碼比較多, 就不方便全都貼出來(lái)了; 我根據功能點(diǎn)貼出來(lái)部分片段吧;

    左側刻度

    左側刻度其實(shí)就是寫(xiě)死的 從00:00 - 01:00 ---> 23:00 - 00:00, 但是在寫(xiě)的時(shí)候有一個(gè)小的問(wèn)題, 就是這個(gè)組件是浮動(dòng)到左側的, 而且他要隨著(zhù)右側條目的滾動(dòng)而滾動(dòng), 其實(shí)一開(kāi)始我寫(xiě)到一個(gè)盒子里了, 滾動(dòng)容器整體就一起滾動(dòng)了, 但是遇到了一個(gè)小問(wèn)題, 由于右側條目會(huì )變得超寬, 就會(huì )出現橫向滾動(dòng)條, 如果橫滾整個(gè)容器的話(huà), 左側的時(shí)間刻度就會(huì )被滾動(dòng)出可視區域.

    所以還是絕對定位之后, 監聽(tīng)右側日程條目的滾動(dòng)事件, 動(dòng)態(tài)的改變左側的style的top值, 反向賦值就好了, 由于是向下滾動(dòng)的, 所以左側的時(shí)間刻度需要向上滾動(dòng), 所以top值取反就會(huì )達到同步的效果; 真是個(gè)小機靈鬼吧, 嘿嘿; 這個(gè)代碼就不占用篇幅了, 大家自由發(fā)揮吧, 如果有更好的方式, 歡迎評論區留言.

    ScheduleItem日程容器條目

    先看下這個(gè)組件的代碼:

    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>
      );
    };
    

    這一部分呢(就是下面灰色一條一條的部分), 為什么要單獨出一個(gè)組件呢? 可以先思考一下......

    好了, 不賣(mài)關(guān)子了, 其實(shí)就是為了好定位用戶(hù)的日程數據, 例如今天的10:00 -- 11:00, 定位到哪里的問(wèn)題.

    還記得這個(gè)API嗎?

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

    這個(gè)組件內會(huì )有[DOMTimeStamp, DOMTimeStamp] 這樣的一個(gè)參數(DOMTimeStamp時(shí)間戳的意思), 這兩個(gè)時(shí)間戳其實(shí)就是當前時(shí)段的 10:00 -- 11:00 的其實(shí)和截至時(shí)間戳, 由于我們接受的startTime和endTime也是時(shí)間戳, 通過(guò)比較大小是否在這個(gè)范圍, 就可以控制展示和隱藏, 這回明白為什么采用時(shí)間戳了吧, 直接比較數字大小就好了;

    我們再說(shuō)一下這個(gè)東東的樣式問(wèn)題:

    其實(shí)這個(gè)東東我我寫(xiě)死了30px, 原因呢就是因為一小時(shí)是60分鐘, 如果60px的話(huà)太高了, 所以寫(xiě)了30px, 方便定位嘛, 畢竟我懶, 不想太復雜的計算;

    所以定位計算也就一行代碼: const calcTop: (startTime: number) => number = (startTime) => moment(startTime).minute() / 2; 高度定位問(wèn)題結了! 哈哈~~

    接下來(lái)呢, 還有一個(gè)問(wèn)題就是高度問(wèn)題, 如圖:

    高度計算其實(shí)也不難, 主要根據當前起止時(shí)間的區間范圍去計算( 1px 兩分鐘 ), 具體實(shí)現看代碼:

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

    首先會(huì )判斷入參的時(shí)間戳是不是只有一個(gè)時(shí)間, 如果只有開(kāi)始時(shí)間, 沒(méi)有結束時(shí)間, 寫(xiě)死30px, 如果有起止時(shí)間, 就去轉成分鐘動(dòng)態(tài)計算一下;

    最后還有一個(gè)問(wèn)題, 業(yè)務(wù)數據是怎么傳進(jìn)去是如何渲染到組件的:

    先看一下我們傳入data字段的JSON:

    [
      {
        startTime: 1626057075000, // 開(kāi)始時(shí)間
        endTime: 1626070875000, // 結束時(shí)間
        value: 'any', // 業(yè)務(wù)數據
      },
      {
        startTime: 1626057075000,
        endTime: 1626070875000,
        value: 'any',
      },
      {
        startTime: 1626057075000,
        endTime: 1626070875000,
        value: 'any',
      },
      {
        startTime: 1626057075000,
        endTime: 1626070875000,
        value: 'any',
      },
    ];
    

    其實(shí)我們在循環(huán)渲染這個(gè)ScheduleItem組件的時(shí)候, 用那個(gè)寫(xiě)死的24h的list去循環(huán), 之后, 循環(huán)的時(shí)候, 動(dòng)態(tài)的去業(yè)務(wù)數據中去查找符合當次循環(huán)的時(shí)間范圍內的業(yè)務(wù)數據, 把這個(gè)數據塞到組件內; 大致代碼如下:

    for (let i = 0; i < HoursList.length; i++) {
          resule.push({
            timestampRange: [todayTime + i * 3600 * 1000, todayTime + (i + 1) * 3600 * 1000],
            dataItem: [ // 由于當前一個(gè)時(shí)間段, 日程可能沖突, 所以要有一個(gè)list傳入組件
              ...data.filter((item) => {
                return (
                  item.startTime >= todayTime + i * 3600 * 1000 &&
                  item.startTime < todayTime + (i + 1) * 3600 * 1000
                );
              }),
            ],
          });
        }
    

    總結

    以上就是這個(gè)組件大部分的實(shí)現, 從接到需求, 到設計組件, 最后到實(shí)現細節, 說(shuō)的不一定面面俱到, 但也算是基本的實(shí)現思想.

    也羅列了一下技術(shù)難點(diǎn)的實(shí)現細節, 其實(shí)看起來(lái)也并不難, 只要稍稍動(dòng)動(dòng)腦就可以了.

    我的實(shí)現方式也不一定很好, 程序的實(shí)現方式千萬(wàn)種, 我只是表達了一下自己的設計思想, 與大家共同學(xué)習, 如果有什么不好的地方, 歡迎大家評論區指出, 我們共同進(jìn)步.

    到此這篇關(guān)于手把手帶你用React擼一個(gè)日程組件的文章就介紹到這了,更多相關(guān)React日程組件內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。

    中文字幕丰满伦子无码| 国外AV无码精品国产精品| 国产女人的高潮国语对白| 黄色无码com网站| 欧美人与动XXXXZ0OZ| 美女大量吞精在线观看456|