鐵人賽
React
javascript
nodejs
鐵人賽第30天,今天我們要將日曆的互動功能完成,不囉唆,立馬上工!
Calendar
是日曆最上層的元件,而子元件Header
及DateContent
均有使用到moment物件
。
因此,我們要在Calendar
設一個state
來存放moment
,並傳給子元件,達到子元件共同存取,確保資料一致。
日曆component
的結構不深,其實用props
傳遞即可,不過,我們還是練習一下使用useContext + useReducer
的方式,設計箭頭按鈕對應month
切換的部分。
useReducer
中的state
,我們設定moment.format("YYYY-MM-DD")
輸出的字串值。
js
檔案中,如下`
Header.js
: Header componentWeekDay.js
: WeekDay componentDateContent.js
: DateContent component
// Calendar.js
import React, {useReducer, createContext} from "react";
import moment from "moment";
import * as Styles from "./styles";
import reducers from "./reducers"
// components
import Header from "./components/Header";
import WeekDay from "./components/WeekDay";
import DateContent from "./components/DateContent";
// context store
export const AppStore = createContext();
// Calendar component
export default function Calendar() {
const mmt = new moment();
const timeReducer = useReducer(reducers, mmt.format("YYYY-MM-DD"));
return (
<AppStore.Provider value={{timeReducer:timeReducer}}>
<div>
<h1>{"Calendar Demo"}</h1>
<div className="calendar-container" style={Styles.calendarContainer}>
<Header />
<WeekDay />
<DateContent />
</div>
</div>
</AppStore.Provider>
);
}
console.log
快速驗證,只要確定reducer
連線上,就可以開始在對應的action
建立對應的程式碼// reducers.js
import moment from "moment";
// reducers func
export default function reducers(stateTime, action){
let type = action.type;
const mmt = new moment(stateTime);
switch(type){
case "LAST_MONTH":
mmt.subtract(1, "month");
break;
case "NEXT_MONTH":
mmt.add(1, "month");
break;
default:
break
}
console.log(mmt.format("YYYY-MM-DD"));
return mmt.format("YYYY-MM-DD");
}
Header
元件// Header.js
import React, {useContext} from "react";
import * as Styles from "../styles";
import { AppStore }from "../Calendar";
import moment from "moment";
export default function Header(){
const { timeReducer } = useContext(AppStore);
const dateInfo = timeReducer[0];
const mmt = new moment(dateInfo);
const dispatch = timeReducer[1];
return (
<div className="header-container" style={Styles.headerContainer}>
<span style={Styles.headerMonthYearStyle} className="month-year">{mmt.format("MMM-YYYY")}</span>
<span style={Styles.headerButtonStyle} onClick={()=>dispatch({type:"LAST_MONTH"})}> {"<"} </span>
<span style={Styles.headerButtonStyle} onClick={()=>dispatch({type:"NEXT_MONTH"})}> {">"} </span>
</div>
)
}
moment
變數,因此,我們要將Calendar
的moment state
傳遞到getWeeksInMonth(mmt)
import getWeeksInMonth from "../utils";
import * as Styles from "../styles";
import { AppStore }from "../Calendar";
import moment from "moment";
export default function DateContent(){
const {timeReducer} = useContext(AppStore);
const date = timeReducer[0];
const mmt = new moment(date);
let weekContentList = getWeeksInMonth(mmt);
let result = [];
return (
<div className="DateContainer" style={Styles.DateContainer}>
{
weekContentList.map((week, wIdx)=>{
let aWeek = [];
aWeek = week.map((day, dIdx)=>
<span className="dateContent-day" style={Styles.dayStyle} key={`${day}-${dIdx}`}>{day===0?"":day}</span>)
return <div className="aweek" style={Styles.aWeekStyle} key={`${week}-${wIdx}`}>{aWeek}</div>;
})
}
{result}
</div>
)
}
getWeeksInMonth()
要接收上層的moment
,故別忘了修改utils.js
的程式喔!// utils.js
// GLobal Vars
const SEVENDAYS = 7; // 建立一個通用變數,存放一週有7天
function processWeekDays(mmt, isFirstWeek=false){
// get first day of a week, ex: Thursday
const totalDays = mmt.daysInMonth();
const startDay = isFirstWeek?
mmt.startOf("month").day():0;
const weekDays = Array(SEVENDAYS).fill(0);
let isFinished = false;
for(let d=startDay; d<SEVENDAYS; d++){
weekDays[d] = mmt.date();
if(mmt.date()!==totalDays)
mmt.add(1, "day");
else{
isFinished=true;
break;
}
}
// console.log("weekDays", weekDays);
return {weekDays, isFinished};
}
export default function getWeeksInMonth(mmt){
const weekDayList = [];
// first weekDays
const {weekDays} = processWeekDays(mmt, true);
weekDayList.push(weekDays);
let loopStatus = false;
// create
while(!loopStatus){
let result = processWeekDays(mmt);
const {weekDays} = result;
weekDayList.push( weekDays );
loopStatus = result["isFinished"];
}
return weekDayList;
}
styles.js
裡的Height
,讓它顯示範圍大一點// styles.js
export const STYLES = {
WIDTH: "280px",
HEIGHT: "320px", //<--adjust 280px -> 320px
ONE_COLUMN_HEIGHT: "40px",
設計重點
,大家可能會疑問,為何不直接給予moment
物件當作初始值即可?而要透過轉換成為字串後,再當作state
的值。component
無法更新,詳細的原因我會持續探究,屆時再更新給朋友們! 如果你有答案,也別忘了指點一下喔!height
,來因應日期的變化!是個不錯的方法,想練習的朋友可別以挑戰看看!!終於! 完賽了!
30天的堅持,證明了自己鐵人的意志,滿滿的肺腑之言我會找時間po上,假日先讓我好好的與家人相聚,因為家人是我陪伴完賽的強力後盾,特別是老婆,這30天感謝有你們的支持,愛你們了!
另外,我也要好好謝謝我的好馬吉Andy Lu
,感謝他揪我一起參加鐵人賽,我們都撐過來了,完成新的里程碑!Rock~~!
最後,照慣例要來個鼓勵,你的堅持與勇氣,讓你散發鑽石般耀眼的光芒!加油!繼續朝前端之神邁進!!