이번엔 react를 사용한 달력 알고리즘을 만드려고 합니다.
완성형이 예쁘다기보단 구조를 보여드리기 위함을 참고해주세요!!
-Moment.js 란
javaScript에서 날짜 데이터 조작을 하기 쉽게 도와주는 라이브러리입니다.
(공식 사이트를 참조하시면 학습에 더욱 도움이 됩니다.)
-React.js 에서 Moment.js 사용
먼저 다음 명령어 npm create-react-app [프로젝트명]를 사용하여 react 파일을 생성해줍니다.
(기존 프로젝트가 있다면 쓰셔도 무방합니다.)
moment.js 는 저희가 캘린더를 만들기 위한 핵심 라이브러리입니다.
CMD에서 오른쪽의 명령어를 통해 인스톨해주세요 npm install moment --save
프로젝트를 생성하셨다면 App.js 파일을 아래와 같이 정리해줍니다.
moment.js를 사용하여 today.format으로 현재 연도 및 달을 표시했습니다.
import './App.css';
import {useState} from 'react';
import moment from 'moment';
const App =()=>{
const [getMoment, setMoment]=useState(moment());
const today = getMoment; // today == moment() 입니다.
return(
<div className="App">
<div className="control">
<button>이전달</button>
<span>{today.format('YYYY 년 MM 월')}</span> //YYYY는 년도 MM 은 달 입니다.
<button>다음달</button>
</div>
<table>
<tbody>
</tbody>
</table>
</div>
);
}
export default App;
아래처럼 화면이 나오실 겁니다. moment()는 선언할 때 현재의 날짜로 초기화되어있습니다.
라이브러리를 어떻게 사용하는지 간단한 방식으로 보여드렸습니다.
사이트를 찾아보시면 moment()를 사용하는 방법이 나옵니다.
subtract는 빼기 add는 더하기입니다. 원하는 날짜 단위를 선택해서 더하고 빼고 합니다.
그래서 useState를 사용하여 아래처럼 달 단위로 날짜를 변경할 수 있게 설정했습니다.
clone() 은 기존의 moment가 아닌 새로운 객체를 반환했다는 의미입니다.
<button onClick={()=>{ setMoment(getMoment.clone().subtract(1, 'month')) }} >이전달</button>
<span>{today.format('YYYY 년 MM 월')}</span>
<button onClick={()=>{ setMoment(getMoment.clone().add(1, 'month')) }} >다음달</button>
화면을 클릭해보시면 날짜의 변경이 보이실 겁니다.
-Calendar 만들기
먼저 첫 주와 마지막 주에 대한 설정을 넣겠습니다.
firstWeek는 startOf('momth') 그 달의 시작하는 week() 주입니다.
lastWeek는 endOf('momth') 그 달의 끝나는 week() 주입니다.
그 달의 시작주와 끝 주을 알게 되었습니다.
const App =()=>{
const [getMoment, setMoment]=useState(moment());
const today = getMoment;
const firstWeek = today.clone().startOf('month').week();
const lastWeek = today.clone().endOf('month').week() === 1 ? 53 : today.clone().endOf('month').week();
lastWeek에서 쓰인 조건문을 보면 1년은 52주가 존재하고 며칠이 더 있는데 이 부분을 달력은
53주로써 표현해야 합니다!! 하지만 moment()는 내년의 첫 주인 1로 표시하기 때문에
마지막 주가 1이라면 53으로 표시합니다.
react에선 배열을 아래와 같이 표시하면 배열 내의 요소를 전부 하기 때문에
const arr = [1, 2, 3, 4, 5];
return (
<div className="App">
<table>
<tbody>
{arr} // 웹에 12345 가 표시됩니다.
</tbody>
</table>
</div>
);
이 방식을 응용하여 각 배열마다 테이블 요소인 <tr></tr>을 넣어주어 달력을 표현하겠습니다.
배열의 표현방식을 설명드렸으니 아래의 코드처럼 사용하면 테이블을 쉽게 만들 수 있습니다.
반복문을 사용하여 해당 달의 총주의 수만큼 반복문을 실행하고 테이블의 내용을 배열에 추가합니다.
const calendarArr=()=>{
let result = [];
let week = firstWeek;
for ( week; week <= lastWeek; week++) {
result = result.concat(
<tr key={week}>
</tr>);
}
return result;
}
return (
<div className="App">
<div className="control">
<button onClick={()=>{ setMoment(getMoment.clone().subtract(1, 'month')) }} >이전달</button>
<span>{today.format('YYYY 년 MM 월')}</span>
<button onClick={()=>{ setMoment(getMoment.clone().add(1, 'month')) }} >다음달</button>
</div>
<table>
<tbody>
{calendarArr()}
</tbody>
</table>
</div>
);
}
export default App;
week 가 설정됐으니 이제 days를 설정해줍니다. week 수만큼 7일 기준으로 반복문을 출력해주면 아래와 같이
출력될 것입니다. index를 사용하여 각주의 시작 day마다 +0부터 +6까지 사용하여 표현하는 것이죠!
<tr key={week}>
{
Array(7).fill(0).map((data, index) => {
let days = today.clone().startOf('year').week(week).startOf('week').add(index, 'day');
return(
<td key={index} >
<span>{days.format('D')}</span>
</td>
);
})
}
</tr>
-중요한 부분 들어갑니다-
원래 moment()는 startOf('year')가 없어도 현재 날짜를 기준으로 하기에 없어도 될 것 같지만 필요합니다!!
저는 moment()가 현재 날짜로 초기화되어있다고 말씀드렸습니다.
저희가 위에서 설정한 53번째 주 같은 경우가 생기면 moment()는 1을 반환하기 때문에 53으로 바꿔주었습니다.
똑같이 days에서 사용한 today 변수도 이런 문제가 생길 수 있습니다.
53주째를 내년으로 출력하여 저희가 원하는 결과가 나오지 않을 수 있기 때문에 연도를 꼭 지정해주는 것입니다.
거의 다 왔습니다.. 힘내세요!!!
저희는 보통 달력을 볼 때 이번 달의 날짜가 아니어도 표현되는 부분이 있습니다.
그리고 컴퓨터로 보는 달력이라면 오늘을 표현해주는 것은 당연한 것이겠지요 바로 코딩해줍니다.
<tr key={week}>
{
Array(7).fill(0).map((data, index) => {
let days = today.clone().startOf('year').week(week).startOf('week').add(index, 'day');
if(moment().format('YYYYMMDD') === days.format('YYYYMMDD')){
return(
<td key={index} style={{backgroundColor:'red'}} >
<span>{days.format('D')}</span>
</td>
);
}else if(days.format('MM') !== today.format('MM')){
return(
<td key={index} style={{backgroundColor:'gray'}} >
<span>{days.format('D')}</span>
</td>
);
}else{
return(
<td key={index} >
<span>{days.format('D')}</span>
</td>
);
}
})
}
</tr>
오늘이면 현재 날짜인 moment(). format을 사용하여 오늘이 맞으면 배경색을 RED
그달에 있는 날짜가 아니라면 GRAY 둘 다 아니라면 색이 없이 나오게 설정해주었습니다.
완성하셨다면 아래처럼 코드가 돼있을 것이고 css도 첨부하였습니다.
App.js
import './App.css';
import {useState} from 'react';
import moment from 'moment';
const App =()=>{
const [getMoment, setMoment]=useState(moment());
const today = getMoment;
const firstWeek = today.clone().startOf('month').week();
const lastWeek = today.clone().endOf('month').week() === 1 ? 53 : today.clone().endOf('month').week();
const calendarArr=()=>{
let result = [];
let week = firstWeek;
for ( week; week <= lastWeek; week++) {
result = result.concat(
<tr key={week}>
{
Array(7).fill(0).map((data, index) => {
let days = today.clone().startOf('year').week(week).startOf('week').add(index, 'day'); //d로해도되지만 직관성
if(moment().format('YYYYMMDD') === days.format('YYYYMMDD')){
return(
<td key={index} style={{backgroundColor:'red'}} >
<span>{days.format('D')}</span>
</td>
);
}else if(days.format('MM') !== today.format('MM')){
return(
<td key={index} style={{backgroundColor:'gray'}} >
<span>{days.format('D')}</span>
</td>
);
}else{
return(
<td key={index} >
<span>{days.format('D')}</span>
</td>
);
}
})
}
</tr>
);
}
return result;
}
return (
<div className="App">
<div className="control">
<button onClick={()=>{ setMoment(getMoment.clone().subtract(1, 'month')) }} >이전달</button>
<span>{today.format('YYYY 년 MM 월')}</span>
<button onClick={()=>{ setMoment(getMoment.clone().add(1, 'month')) }} >다음달</button>
</div>
<table>
<tbody>
{calendarArr()}
</tbody>
</table>
</div>
);
}
export default App;
App.css
html, body {
height: 100%;
}
.App {
height:100vh;
width:100vw;
font-size: 1.5vh;
display: flex; /* display 설정해줘야 아래 flex로 해주면 direction 선택가능*/
flex-direction: column; /* 이건 세로순으로 태그들을 표시함 css*/
align-items: center; /* 이건 가로 중앙 css*/
justify-content: center; /* 이건 세로 중앙 css*/
}
.App .control{
display: flex;
flex-direction: row;
}
.App table{
display:flex;
width:50vw;
height:50vh;
}
.App table tbody{
display:flex;
flex-direction: column;
}
.App tr{
display:flex;
flex-direction: row;
}
.App td{
display:flex;
border:1px solid gray;
width:5vw;
height:5vh;
}
.App td span{
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
아래와 같은 화면이 잘 나오고 버튼도 잘 적용이 되셨나요?
축하드립니다. UI 만 따로 코딩하시면 보다 예쁜 달력이 될 것이며 조금만 응용한다면
여러분만의 다양한 기능의 커스텀 달력을 만드실 수 있을 겁니다. 성실한 코딩 하세요!!