实现迷你Calendar
组件:
interface CalendarProps{
defaultValue:Date,
onChange?:(date:Date)=>void
}
function Calendar(props:CalendarProps) {
const {defaultValue,onChange}:CalendarProps =props
const [date,setDate] =useState(defaultValue)
const handlePrevMonth = ()=>{
setDate(new Date(date.getFullYear(),date.getMonth()-1,1))
}
const handleNextMonth =()=>{
setDate(new Date(date.getFullYear(),date.getMonth()+1,1))
}
const daysOfMonth =(year:number,month:number)=>{
return new Date(year,month+1,0).getDate()
}
const firstDayOfMonth =(year:number,month:number)=>{
return new Date(year,month,1).getDay()
}
const renderDates =()=>{
const days=[]
const daysCount=daysOfMonth(date.getFullYear(),date.getMonth())
const firstDay=firstDayOfMonth(date.getFullYear(),date.getMonth())
for(let i=0;i<firstDay;i++){
days.push(<div className="empty" key={`empty-${i}`}></div>)
}
for(let i=1;i<=daysCount;i++){
const clickHanlder=()=>{
const currentDate=new Date(date.getFullYear(),date.getMonth(),i)
onChange?.(currentDate)
}
if(i===date.getDate()){
days.push(<div className="day selected" key={`day-${i}`} onClick={clickHanlder}>{i}</div>)
}else{
days.push(<div className="day" key={`day-${i}`} onClick={clickHanlder}>{i}</div>)
}
}
return days
}
return (
<div className="calendar">
<div className="header">
<button onClick={handlePrevMonth}><</button>
<div>{date.getFullYear()} 年 {date.getMonth()+1} 月</div>
<button onClick={handleNextMonth}>></button>
</div>
<div className="days">
{renderDates()}
</div>
</div>
);
}
export default Calendar;
样式:
.calendar {
border: 1px solid #000;
padding: 10px;
width: 300px;
height: 250px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.days {
display: flex;
flex-wrap: wrap;
}
.day, .empty {
width: calc(100% / 7);
text-align: center;
line-height: 30px;
}
.day:hover, .selected {
background-color: #ccc;
cursor: pointer;
}
使用:
const App = () => {
return (
<>
<Calendar defaultValue={new Date()} onChange={(date)=>{
console.log(date)
alert(date.toLocaleDateString())
}} />
</>
);
};
暴露Calendar组件api:
interface CalendarRef {
getDate:()=>Date
setDate:(date:Date)=>void
}
const Calendar = forwardRef<CalendarRef,CalendarProps>((props,ref)=> {
const {defaultValue,onChange}:CalendarProps =props
const [date,setDate] =useState(defaultValue)
useImperativeHandle(ref,()=>{
return {
getDate:()=>date,
setDate:(date:Date)=>{
setDate(date)
}
}
})
}
使用:
const App = () => {
const calendarRef = useRef<CalendarRef>(null)
useEffect(() => {
console.log(calendarRef.current?.getDate().toLocaleDateString());
setTimeout(() => {
calendarRef.current?.setDate(new Date(2024, 3, 1));
}, 3000);
}, []);
return (
<>
<Calendar ref={calendarRef} defaultValue={new Date()} onChange={(date)=>{
console.log(date)
alert(date.toLocaleDateString())
}} />
</>
);
};
同时支持受控和非受控:
pnpm i -save ahook
const [date,setDate] =useControllableValue(props,{
defaultValue: new Date(),
})
const clickHanlder=()=>{
const currentDate=new Date(date.getFullYear(),date.getMonth(),i)
setDate(currentDate)
}
<Calendar value={new Date()} onChange={(date)=>{alert(date.toLocaleDateString()) }} />