繼承前篇,本篇就要來實作 useDate!
日期,不是約會
許多後台或是查詢資料的介面上,都會常用到搜尋日期的功能,雖然並不是什麼特別的搜尋條件,但有的時候為了塗個使用者體驗,需要進行時間「平移」(往前往後)、或是調整直接選「幾天前」等功能,一個畫面刻一次還好,十個畫面都要用的話,就是 hook 出現的時候了!
先來打造以一個日期(欄位)為主的需求:
請準備好你的 date-fns,這邊要使用的是:
format
add
sub
日期之中會有一些格式與設定,這邊先建立一組預設值:
const defaultConfigs = {
defaultDate: null,
formatPattern: "yyyy-MM-dd",
transitionUnit: "days",
offsetUnit: "days",
offsetValue: 0,
}
defaultDate
: 預設為當下日期formatPattern
: 日期最後要輸出的格式,可參照 date-fns 的定義transitionUnit
: 平移的單位,以 days 就是「天」,可參照 date-fns 的定義offsetUnit
: 偏差的單位,最後輸出之前會先偏差日期,例如搜尋要從七天前開始,就可以給定 -7offsetValue
: 如上,正負有差別一樣是透過 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 本身已經代表了要加或減了,因此都改正數為主。
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
: 到指定時間看來有 date-fns 幫助下算時間真的輕鬆許多了!
現在只有一個,兩個時間之間的互動可以直接宣告兩個 useDate 來使用,或是我們也可以組另一個 hook 來使用,就留到下篇吧!