承繼上篇,useDate 變兩個就成了 useDates :)
除了用一個 useDate 之外,有時候需要兩個關聯的時間(區間)來提供進階的搜尋,這個就稍微麻煩了一些,例如:前後的時間不能交錯、不能選過去的時間、或不能選大於幾個月的時間...等等
外加今天比較晚開工,只好先粗暴一點了
useDate 既有功能之外,再加上一些限制:
這次有用到
add
sub
format
isFuture
isPast
isAfter
intervalToDuration
由於有兩個日期,我們改存 array [new Date(), new Date()]
,程式碼通通給他 map 下去 (#`д´)ノ
function datesReducer(state, action) {
const type = action.type
const payload = action.payload
const configs = action.configs
switch (type.toUpperCase()) {
case "BACKWARD": {
return state.map((date) =>
sub(date, { [configs.transitionUnit]: Math.abs(payload || 1) })
)
}
case "FORWARD": {
return state.map((date) =>
add(date, { [configs.transitionUnit]: Math.abs(payload || 1) })
)
}
case "NOW": {
return [new Date(), new Date()]
}
case "FIRST_TO": {
return [new Date(payload), state[1]]
}
case "SECOND_TO": {
return [state[0], new Date(payload)]
}
default: {
return state
}
}
}
function useDates(configs) {
const mergedConfigs = merge(defaultConfigs, configs)
const [dates, dispatch] = useReducer(
datesReducer,
mergedConfigs.defaultDate,
(defaultDate) =>
defaultDate
? [new Date(defaultDate), new Date(defaultDate)]
: [new Date(), new Date()]
)
const datesDispatch = useCallback(
(type, payload) => dispatch({ type, payload, configs: mergedConfigs }),
[configs]
)
const offsetDates = dates.map((date) =>
offset(date, mergedConfigs.offsetUnit, mergedConfigs.offsetValue)
)
const formatDate = offsetDates.map((date) =>
format(date, mergedConfigs.formatPattern)
)
return [formatDate, datesDispatch]
}
const defaultConfigs = {
defaultDate: null,
formatPattern: "yyyy-MM-dd",
transitionUnit: "days",
offsetUnit: "days",
offsetValue: 0,
/* NEW */
durationUnit: "days",
durationDateOnIssue: 0, // 1 || 2 || 0
minDuration: 0,
maxDuration: 0,
noPastDate: false,
noFutureDate: false,
noGreaterThenSecondDate: false,
}
來控制兩個日期的區間
durationUnit
: 區間單位,預設為天durationDateOnIssue
: 當區間不符合時,要校正回來的日期,0 不進行任何變更(回傳修改前的時間),1 就是校正第一個(index 0),2 就是第二個(indminDuration
: 最短區間maxDuration
: 最大區間noPastDate
: 不能回到過去noFutureDate
: 也不能穿梭未來noGreaterThenSecondDate
: 更不能讓時間交疊 (? (意即第一天比第二天晚)由於多了很多內容,我們把 reducer 修改為這個流程
就是原本的 dateReducer 改名為 dateChanger
當有設定 (minDuration || maxDuration)
且 durationDateOnIssue
為 1 or 2 時,這邊會進行日期的校正,超過太多,就扣會來;日期不過,就加回來,最後回傳調整完的日期。
durationDateOnIssue
則是指定要校正的日期是第一個還是第二個,可以再進一步透過durationUnit
來設定要判斷以及校正的單位。
若 durationDateOnIssue
為 0,且不符合任一 min/max 時,會 fallback,回傳修改前的日期。
function datesDurationChecker(prevDates, changedDates, action) {
const configs = action.configs
const durationDateOnIssue = configs.durationDateOnIssue
const minDuration = configs.minDuration
const maxDuration = configs.maxDuration
const durationUnit = configs.durationUnit
const duration = intervalToDuration({
start: changedDates[0],
end: changedDates[1],
})
const isLesser = duration[durationUnit] < minDuration
const lesserDelta = minDuration - duration[durationUnit]
const isLarger = duration[durationUnit] > maxDuration
const largerDelta = duration[durationUnit] - maxDuration
if (minDuration > 0 && isLesser) {
if (!durationDateOnIssue) return prevDates
return changedDates.map((date, index) => {
if (index + 1 === durationDateOnIssue) {
return add(date, { [durationUnit]: lesserDelta })
}
return date
})
}
if (maxDuration > 0 && isLarger) {
if (!durationDateOnIssue) return prevDates
return changedDates.map((date, index) => {
if (index + 1 === durationDateOnIssue) {
return sub(date, { [durationUnit]: largerDelta })
}
return date
})
}
return changedDates
}
這個就比較單純,判斷有沒有通過各種限制,沒有就沒有,有就有,白紙黑字(??
function datesLimitationChecker(dates, action) {
const configs = action.configs
if (configs.noPastDate && dates.some(isPast)) {
return false
}
if (configs.noFutureDate && dates.some(isFuture)) {
return false
}
if (configs.noGreaterThenSecondDate && isAfter(dates[0], dates[1])) {
return false
}
return true
}
按照流程稍微編排一下,並最後透過 datesLimitationChecker
的 boolean 進一步判斷要回傳新的日期或是舊的日期。
function datesReducer(state, action) {
const changedDate = datesChanger(state, action)
const fixedDate = datesDurationChecker(state, changedDate, action)
return datesLimitationChecker(fixedDate, action) ? fixedDate : state
}
這樣就完成啦!
useDate 以及 useDates 都在 date-fns 加持下都讓我們很方便操控時間,也能專心處理邏輯。
由於可以設定的限制邊多,功能上可以新增 error message 進一步提醒使用者哪裡不對,提升一些 UX。
或者,安裝別人寫好的 date picker,可選擇可惜不在同一隊的查理狐狐刻了 mui-date-picker,應該也是不錯的選擇啦 XDD