iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

React Hook 不求人,建立自己的 Hook Libary系列 第 22

[DAY 22] 自己的 Hook 自己做! useDate 來操控日期吧!

  • 分享至 

  • xImage
  •  

image alt

繼承前篇,本篇就要來實作 useDate!

日期,不是約會

DEMO 在這裡

情境

許多後台或是查詢資料的介面上,都會常用到搜尋日期的功能,雖然並不是什麼特別的搜尋條件,但有的時候為了塗個使用者體驗,需要進行時間「平移」(往前往後)、或是調整直接選「幾天前」等功能,一個畫面刻一次還好,十個畫面都要用的話,就是 hook 出現的時候了!

功能

先來打造以一個日期(欄位)為主的需求:

  • 時間可以平移,即往前一天/往後一天
  • 時間可以回到今天日期
  • 可以給預設日期
  • 可以計算幾天前後(偏差)
  • 不只「天」,也能接受其他單位的移動
  • 可以指定輸出相關格式

開始!

請準備好你的 date-fns,這邊要使用的是:

  • format
  • add
  • sub

Config

日期之中會有一些格式與設定,這邊先建立一組預設值:

const defaultConfigs = {
  defaultDate: null,
  formatPattern: "yyyy-MM-dd",
  transitionUnit: "days",
  offsetUnit: "days",
  offsetValue: 0,
}
  • defaultDate: 預設為當下日期
  • formatPattern: 日期最後要輸出的格式,可參照 date-fns 的定義
  • transitionUnit: 平移的單位,以 days 就是「天」,可參照 date-fns 的定義
  • offsetUnit: 偏差的單位,最後輸出之前會先偏差日期,例如搜尋要從七天前開始,就可以給定 -7
  • offsetValue: 如上,正負有差別

Hook 本身

一樣是透過 useReducer 來完成,實際儲存的會是 new Date() 產生出來的 Date Object,以利後續的操作與時間的正確性。

function useDate(configs) {
  /* 單純合併兩個物件,語意化 */
  const mergedConfigs = merge(defaultConfigs, configs)

  const [date, dispatch] = useReducer(
    dateReducer,
    mergedConfigs.defaultDate,
    (defaultDate) => (defaultDate ? new Date(defaultDate) : new Date())
  )

  /* dispatch 懶人化包裝 */
  const dateDispatch = useCallback(
    (type, payload) => dispatch({ type, payload, configs: mergedConfigs }),
    [configs]
  )

  /* 偏差計算 */
  const offsetDate = offset(
    date,
    mergedConfigs.offsetUnit,
    mergedConfigs.offsetValue
  )
  
  /* 格式文字 */
  const formatDate = format(offsetDate, mergedConfigs.formatPattern)

  return [formatDate, dateDispatch]
}

/* 偏差計算 */
function offset(date, unit, value) {
  if (value === 0) return date

  if (value > 0) {
    return add(date, { [unit]: value })
  }

  if (value < 0) {
    return sub(date, { [unit]: Math.abs(value) }) //value < 0 用 abs 取絕對值
  }
}

add, sub 本身也吃負數,但這樣可能會有點混亂,因為 add/sub 本身已經代表了要加或減了,因此都改正數為主。

reducer 本身

function dateReducer(state, action) {
  const type = action.type
  const payload = action.payload
  const configs = action.configs

  switch (type.toUpperCase()) {
    case "BACKWARD": {
      return sub(state, { [configs.transitionUnit]: Math.abs(payload || 1) })
    }
    case "FORWARD": {
      return add(state, { [configs.transitionUnit]: Math.abs(payload || 1) })
    }
    case "NOW": {
      return new Date()
    }
    case "TO": {
      return new Date(payload)
    }
    default: {
      return state
    }
  }
}
  • BACKWARD: 往後(過去)
  • FORWARD: 往前(未來)
  • NOW: 今天
  • TO: 到指定時間

DEMO 在這裡

結語

看來有 date-fns 幫助下算時間真的輕鬆許多了!

現在只有一個,兩個時間之間的互動可以直接宣告兩個 useDate 來使用,或是我們也可以組另一個 hook 來使用,就留到下篇吧!


上一篇
[DAY 21] 不要再掐指算時辰啦!與時間套件一起準備當時空旅人吧!
下一篇
[DAY 23] 自己的 Hook 自己做! useDates 兩個時間恰恰好
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言