摘要:然后使用,在的上綁定了,很簡單。日歷遍歷的思路這一次的提交主要是確定了日歷組件,怎么寫,具體思路看下面的代碼,盡量通過注釋把思路講的清楚一些。
前言在最近的項(xiàng)目中,大量的嘗試了react hooks,我們的組件庫用的是Next,除了一個(gè)地方因?yàn)橐褂肍orm + Field的組合,所以使用了class組件,經(jīng)過了這個(gè)項(xiàng)目,也算是總結(jié)一些使用的經(jīng)驗(yàn),所以準(zhǔn)備自己封裝一個(gè)日歷組件來分享一下。以下也會通過git中的commit記錄,來分析我的思路。
下來看下效果(因?yàn)檐浖脑?,轉(zhuǎn)的gif竟然如此抖動(dòng),順便求一個(gè)mac上轉(zhuǎn)換gif比較好用的軟件)
同時(shí)奉上代碼地址: 倉庫
初始化項(xiàng)目只是使用了create-react-app進(jìn)行了項(xiàng)目的搭建,然后總體就是hooks + typescript,當(dāng)然寫的不夠好,就沒有往npm上發(fā)的意愿。
工具方法,保存狀態(tài)首先是寫了一個(gè)工具方法,作用是將時(shí)間戳轉(zhuǎn)化成 年月日 三個(gè)屬性。
interface Res {
year: number;
month: number;
day: number;
}
export const getYearMonthDay = (value: number):Res => {
const date = new Date(value);
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDay()
}
}
然后使用useState,在input的dom上綁定了value,很簡單。
const Test:React.FC = memo(({value = Date.now(), onChange}) => {
console.log("render -------");
const [date, setDate] = useState<Date>(new Date(value));
const { year, month, day } = getYearMonthDay(date.getTime());
console.log(year, month, day);
return (
<div>
<input type="text" value={`${year} - ${month} - ${day}`} />
div>
)
});
日歷遍歷的思路
這一次的提交主要是確定了日歷組件,怎么寫,具體思路看下面的代碼,盡量通過注釋把思路講的清楚一些。
// 以下兩個(gè)數(shù)組只是為了遍歷
const ary7 = new Array(7).fill("");
const ary6 = new Array(6).fill("");
const Test: React.FC = memo(({ value = Date.now(), onChange }) => {
const [date, setDate] = useState<Date>(new Date(value));
// useState保存下面彈窗收起/展開的狀態(tài)
const [contentVisible, setContentVisible] = useState(true);
const { year, month, day } = getYearMonthDay(date.getTime());
// 獲取當(dāng)前選中月份的第一天
const currentMonthFirstDay = new Date(year, month, 1);
// 判斷出這一天是星期幾
const dayOfCurrentMonthFirstDay = currentMonthFirstDay.getDay();
// 然后當(dāng)前日歷的第一天就應(yīng)該是月份第一天向前移幾天
const startDay = new Date(currentMonthFirstDay.getTime() - dayOfCurrentMonthFirstDay * 1000 * 60 * 60 * 24);
const dates:Date[] = [];
// 生成一個(gè)長度為42的數(shù)組,里面記錄了從第一天開始的每一個(gè)date對象
for (let index = 0; index < 42; index++) {
dates.push(new Date(startDay.getTime() + 1000 * 60 * 60 * 24 * index));
}
return (
<div>
<input type="text" value={`${year} - ${month} - ${day}`} />
{
contentVisible && (
<div>
<div>
<span><span>
<span>< <span>
<span>span>
<span>>span>
<span>> >span>
div>
<div>
// 生成的日歷應(yīng)該是7*6的,然后遍歷出42個(gè)span, 這就是之前生成的兩個(gè)常量數(shù)組的作用
{
ary6.map((_, index) => {
return (
<div>
{
ary7.map((__, idx) => {
const num = index * 7 + idx;
console.log(num);
const curDate = dates[num]
return (
<span>{curDate.getDate()}span>
)
})
}
div>
)
})
}
div>
div>
)
}
div>
)
});
處理document點(diǎn)擊事件
const Test: React.FC = memo(({ value = Date.now(), onChange }) => {
// 使用useRef保存最外一層包裹的div
const wrapper = useRef(null);
// 展開收起的方法,都使用了useCallback,傳入空數(shù)組,讓每次渲染都返回相同的函數(shù)
const openContent = useCallback(
() => setContentVisible(true),
[]
);
const closeContent = useCallback(
() => setContentVisible(false),
[]
);
const windowClickhandler = useCallback(
(ev: MouseEvent) => {
let target = ev.target as HTMLElement;
if(wrapper.current && wrapper.current.contains(target)) {
} else {
closeContent();
}
},
[]
)
// 使用useEffect模擬componentDidMount和componentWillUnmount的生命周期函數(shù),來綁定事件
useEffect(
() => {
window.addEventListener("click",windowClickhandler);
return () => {
window.removeEventListener("click", windowClickhandler);
}
},
[]
)
return (
<div ref={wrapper}>
// 之前的那些東西,沒有變化
div>
)
});
處理每個(gè)日期的點(diǎn)擊事件
// 使用setDate,處理,這里其實(shí)第二個(gè)參數(shù)傳遞一個(gè)空數(shù)組即可,因?yàn)檫@個(gè)函數(shù)是不依賴當(dāng)前的date狀態(tài)來變化的。
const dateClickHandler = useCallback(
(date:Date) => {
setDate(date);
const { year, month, day } = getYearMonthDay(date.getTime());
onChange(`${year}-${month}-${day}`);
setContentVisible(false);
},
[date]
)
dateClickHandler(curDate)}>{curDate.getDate()}
支持value傳遞
// 先判斷以下value是否傳遞,如果沒傳默認(rèn)就是當(dāng)前的時(shí)間
const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
let initialDate: Date;
if (value) {
let [year, month, day] = value.split("-");
initialDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
} else {
initialDate = new Date();
}
const [date, setDate] = useState<Date>(initialDate);
}
月份切換的事件處理
月份處理就是當(dāng)前月份的第一天向前移動(dòng)一個(gè)月或者一個(gè)月等等。
const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
// 之前這里的currentMonthFirstDay是直接由date得出的,現(xiàn)在成為組件的state就可以讓他支持變化。
const { year, month, day } = getYearMonthDay(date.getTime());
const [currentMonthFirstDay, setCurrentMonthFirstDay] = useState<Date>(new Date(year, month, 1));
const { year: chosedYear, month: chosedMonth } = getYearMonthDay(currentMonthFirstDay.getTime());
const dayOfCurrentMonthFirstDay = currentMonthFirstDay.getDay();
const startDay = new Date(currentMonthFirstDay.getTime() - dayOfCurrentMonthFirstDay * 1000 * 60 * 60 * 24);
const dates: Date[] = [];
for (let index = 0; index < 42; index++) {
dates.push(new Date(startDay.getTime() + 1000 * 60 * 60 * 24 * index));
}
const openContent = useCallback(
() => setContentVisible(true),
[]
);
const closeContent = useCallback(
() => setContentVisible(false),
[]
);
const windowClickhandler = useCallback(
(ev: MouseEvent) => {
let target = ev.target as HTMLElement;
if (wrapper.current && wrapper.current.contains(target)) {
} else {
closeContent();
}
},
[]
);
const dateClickHandler = useCallback(
(date: Date) => {
const { year, month, day } = getYearMonthDay(date.getTime());
onChange(`${year}-${month + 1}-${day}`);
setContentVisible(false);
},
[date]
);
// 這里所有的月份切換事件都選擇了使用了函數(shù)式更新
const prevMonthHandler = useCallback(
() => {
setCurrentMonthFirstDay(value => {
let { year, month } = getYearMonthDay(value.getTime());
if (month === 0) {
month = 11;
year--;
} else {
month--;
}
return new Date(year, month, 1)
})
},
[]
);
const nextMonthHandler = useCallback(
() => {
setCurrentMonthFirstDay(value => {
let { year, month } = getYearMonthDay(value.getTime());
if (month === 11) {
month = 0;
year++;
} else {
month++;
};
return new Date(year, month, 1);
})
},
[]
);
const prevYearhandler = useCallback(
() => {
setCurrentMonthFirstDay(value => {
let { year, month } = getYearMonthDay(value.getTime());
return new Date(--year, month, 1)
})
},
[]
);
const nextYearHandler = useCallback(
() => {
setCurrentMonthFirstDay(value => {
let { year, month } = getYearMonthDay(value.getTime());
return new Date(++year, month, 1)
})
},
[]
)
return (
<div ref={wrapper}>
<input type="text" value={`${year} - ${month + 1} - ${day}`} onFocus={openContent} />
{
contentVisible && (
<div className="content">
<div className="header">
<span onClick={prevYearhandler}>< <span>
<span onClick={prevMonthHandler}><span>
<span>{`${chosedYear} - ${chosedMonth + 1}`}span>
<span onClick={nextMonthHandler}>>span>
<span onClick={nextYearHandler}>> >span>
div>
div>
)
}
div>
)
};
處理porps變化
工具方法
export const getDateFromString = (str: string):Date => {
let [year, month, day] = str.split("-");
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
組件
const DatePicker: React.FC = ({ value = "", onChange = () => { } }) => {
let initialDate: Date;
if (value) {
initialDate = getDateFromString(value);
} else {
initialDate = new Date();
};
const [date, setDate] = useState<Date>(initialDate);
// 使用了useRef來保存上一次渲染時(shí)傳遞的value值,
const prevValue = useRef(value);
useEffect(
() => {
// 僅當(dāng)value值變化且不同與上一次值時(shí),才會重新進(jìn)行改變自身date狀態(tài)。
if (prevValue.current !== value) {
let newDate = value ");new Date();
setDate(newDate);
const { year, month } = getYearMonthDay(newDate.getTime());
setCurrentMonthFirstDay(new Date(year, month, 1))
}
},
[value]
)
return (
...
)
};
最后這里現(xiàn)在想來其實(shí)也可以用useMemo來處理傳遞進(jìn)來的value值,這也是一種思路。稍后實(shí)現(xiàn)一下。
最后貼下代碼github地址。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/6921.html
摘要:所以我們做的事情其實(shí)就是,聲明了一個(gè)狀態(tài)變量,把它的初始值設(shè)為,同時(shí)提供了一個(gè)可以更改的函數(shù)。 你還在為該使用無狀態(tài)組件(Function)還是有狀態(tài)組件(Class)而煩惱嗎? ——擁有了hooks,你再也不需要寫Class了,你的所有組件都將是Function。 你還在為搞不清使用哪個(gè)生命周期鉤子函數(shù)而日夜難眠嗎? ——擁有了Hooks,生命周期鉤子函數(shù)可以先丟一邊了。 你在還...
摘要:第一個(gè)功能是普通經(jīng)典類組件,也就是眾所周知的有狀態(tài)組件。我們準(zhǔn)備創(chuàng)建一個(gè)上下文環(huán)境來存放全局狀態(tài),然后把它的包裹在一個(gè)有狀態(tài)組件中,然后用來管理狀態(tài)。接下來我們需要用有狀態(tài)組件包裹我們的,利用它進(jìn)行應(yīng)用狀態(tài)的管理。 原文地址對于想要跳過文章直接看結(jié)果的人,我已經(jīng)把我寫的內(nèi)容制作成了一個(gè)庫:use-simple-state,無任何依賴(除了依賴 react ),只有3kb,相當(dāng)輕量。 ...
摘要:前言由于最近接到一個(gè)需要支持拖拽選擇日期的日歷需求,做出來感覺體驗(yàn)和效果都還不錯(cuò),所以今天想跟大家分享一下封裝這個(gè)日歷組件的過程。其中,代表該日期當(dāng)前的狀態(tài),主要是用以區(qū)分用戶在拖拽操作日歷時(shí),有沒有選中該日期。 1. 前言 由于最近接到一個(gè)需要支持拖拽選擇日期的日歷需求,做出來感覺體驗(yàn)和效果都還不錯(cuò),所以今天想跟大家分享一下封裝這個(gè)日歷組件的過程。 2. 調(diào)研開始 正所謂磨刀不誤砍柴...
摘要:為什么選擇是開發(fā)和維護(hù)的一種面向?qū)ο蟮木幊陶Z言。一在組件組件復(fù)用狀態(tài)邏輯很難沒有提供將可復(fù)用性行為附加到組件的途徑例如,把組件連接到。如此很容易產(chǎn)生,并且導(dǎo)致邏輯不一致。同時(shí),這也是很多人將與狀態(tài)管理庫結(jié)合使用的原因之一。 前端時(shí)間,接觸了hooks,研究了一段時(shí)間后感覺使用起來十分方便,正好公司開了一個(gè)新的小項(xiàng)目,正好使用hooks來實(shí)踐一下。 技術(shù)選型 1.為什么選擇umi 在之前...
摘要:難道還不允許設(shè)計(jì)得對新人更友好了我們先把做成就是找罵啊這怎么怪到我們頭上了事實(shí)是,即使在內(nèi)部,也顯然不是所有程序員都熟悉函數(shù)式編程的概念。 1.前言介紹 歷史React在2013年開源,在2015引入函數(shù)式組件,不過在日常開發(fā)中經(jīng)常被忽略。ReactJS Core Team 確實(shí)大部分成員都曾在推特上公開夸贊過對函數(shù)式編程 與 ML 系語言(或其特性)的優(yōu)點(diǎn):Sebastian 日常提...
閱讀 2002·2023-04-26 01:41
閱讀 2500·2021-11-24 09:39
閱讀 1940·2021-11-24 09:38
閱讀 1968·2021-11-19 09:40
閱讀 3789·2021-11-11 11:02
閱讀 3311·2021-10-20 13:48
閱讀 3205·2021-10-14 09:43
閱讀 4433·2021-09-02 15:11