ReactNative Project - 투두리스트 + 달력 [1]

bi_sz·2023년 10월 26일
1

ReactNative

목록 보기
15/37
post-thumbnail

2주만에 다시 RN을 하려니 가물가물하네요.. ㅠ^ㅠ

새 마음 새 뜻으로 프로젝트 생성부터 해 보겠습니다..

프로젝트 생성

> npx create-expo-app todo-calendar

명령어로 expo 프로젝트를 생성해주었습니다.


날짜 다루기

날짜는 라이브러리 없이 new Date 로 관리가 가능합니다.
현재 날짜, 현재 시간 등을 받아오는 작업등을 할 수 있습니다.

어플에서 사용하는 작업중에서는 단순한 기능보다는, 날짜에서 하루를 빼고, 며칠을 더하고 비교하는 등의 정교한 작업을 필요로합니다.

정교한 작업을 위해서는 날짜를 쉽게 다루게 해주는 라이브러리를 사용하는 것이 좋습니다.

날씨 라이브러리

  • moment.js
  • day.js

신규개발을 중단하고 번들사이즈가 큰 moment.js 보다는 day.js 사용을 지향합니다. 사용법은 유사합니다.


day.js 설치하기

yarn을 통해 day.js를 설치해주었습니다.


day.js 예제

import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
dayjs.extend(isBetween);
dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);

export const runPracticeDayjs = () => {
  const hour = new Date().getHours();
  console.log('hour', hour);

  const now = dayjs("2022-11-04 16:01:30");
  console.log("===== Practice Dayjs =====");
  console.log(
    "1. set minute - hh",
    dayjs(now).set("minute", 5).format("YYYY.MM.DD hh:mm:ss a A")
  );
  console.log(
    "2. set minute - HH",
    dayjs(now).set("minute", 5).format("YYYY.MM.DD HH:mm:ss")
  );
  console.log(
    "3. set hour",
    dayjs(now).set("hour", 10).format("YYYY.MM.DD HH:mm:ss")
  );
  console.log("4. get year", dayjs(now).get("year"));
  console.log("5. get month", dayjs(now).get("month")); // 0~11(1월~12월)
  console.log("6. get date", dayjs(now).get("date"));
  console.log("7. get day", dayjs(now).get("day")); // 0:일 ~ 6:토
  console.log("8. get second", dayjs(now).get("second"));
  console.log(
    "9. add hour",
    dayjs(now).add(3, "hour").format("YYYY.MM.DD HH:mm:ss")
  );
  console.log(
    "10. subtract hour",
    dayjs(now).subtract(3, "hour").format("YYYY.MM.DD HH:mm:ss")
  );
  console.log("11. startOf", dayjs(now).startOf("month").format("YYYY.MM.DD"));
  console.log("12. endOf", dayjs(now).endOf("month").format("YYYY.MM.DD"));
  const aDate = dayjs("2022-10-29 15:00:20");
  const bDate = dayjs("2022-10-29 16:00:00");
  console.log("13. isSame month", dayjs(aDate).isSame(bDate, "month"));
  console.log("14. isSame hour", dayjs(aDate).isSame(bDate, "hour"));
  console.log("15. isBefore", dayjs(aDate).isBefore(bDate));
  console.log("16. isBefore date", dayjs(aDate).isBefore(bDate, "date"));
  console.log("17. isAfter a,b", dayjs(aDate).isAfter(bDate));
  console.log("18. isAfter b,a", dayjs(bDate).isAfter(aDate));
  console.log("19. isSameOrBefore", dayjs(aDate).isSameOrBefore(bDate, "date"));
  console.log("20. isSameOrAfter", dayjs(aDate).isSameOrAfter(bDate, "date"));
  console.log(
    "21. isBetween",
    dayjs("2022-10-29 15:30:00").isBetween(aDate, bDate)
  );
  console.log(
    "22. isBetween date",
    dayjs("2022-10-29 15:30:00").isBetween(aDate, bDate, "date")
  );
  console.log("23. diff minute a,b", dayjs(aDate).diff(bDate, "minute"));
  console.log("24. diff minute b,a", dayjs(bDate).diff(aDate, "minute"));
};

src 폴더를 생성하고 practice-dayjs.js 파일을 생성하여 예제코드를 살펴보았습니다.

날짜 관련은 다른 곳에서도 다루기 떄문에 자세하게 살펴보지는 않았습니다.

format

YYYY : 년 (4자리)
MM : 월 (2자리)
DD : 일 (2자리)
HH : 시간 (24시간제)
hh : 시간 (12시간제)
mm : 분
ss : 초
a : 오전
A : 오후


month : 0~11 (1월~12월)
day : 0~6 (일~토)
subtract : 날짜 빼기
startOf : 맨 처음 날짜
endOf : 맨 마지막 날짜
isSame : 날짜가 같은지 비교
isBefore : 해당 날짜보다 이전인지 비교
isAfter : 해당 날짜보다 이후인지 비교
isSameBefore : 해당 날짜보다 같거나 이전인지 비교
isSameAfter : 해당 날짜보다 같거나 이후인지 비교
isBetween : 해당 날짜 사이에 있는지 비교
diff : 두 날짜의 차이
단위가 없는 경우 : ms 까지 비교

isBetween, isSameOrBefore, isSameORAfter 함수는 import 해주고 day.jsextend를 해주어야 사용 가능합니다.


달력 데이터 만들기

윈도우에 보이는 캘린더입니다.

캘린더를 하나의 배열로 보자면 총 35가지의 원소가 담긴 배열이 필요합니다.

7개의 컬럼이 있고, 5개의 열이 있습니다.


getCalendarColumns

App.jscolumns,now를 추가하여, 현재 시간을 기준으로 캘린더에 담길 컬럼을 가져와줍니다.

useEffect 랜더링 직후에 실행되는 함수에서 콘솔로 찍어주었습니다.
빼먹은 day.jsuseEffectimport 해주었습니다.

위에 로그들은 예제코드의 로그들이고 가장 아래의 columns을 확인해줍니다.

columns의 내용을 정렬해주기 위해 https://jsonformatter.org/ 페이지에 복붙해주었습니다.

정렬된 내용을 살펴보니 10월 1일부터 11월 4일까지의 값을 가지고 있습니다.

cloumns 값을 가져와 주는 getCalendarColumns 함수에 대해 살펴보겠습니다.


현재날짜 기준 columns 채우기

export const getCalendarColumns = (now) => {
  const start = dayjs(now).startOf("month"); // 10월 1일
  const end = dayjs(now).endOf("month"); // 10월 31일 
  const endDate = dayjs(end).get("date"); //31

  const columns = [];
  for (let i = 0; i < endDate; i += 1) {
    const date = dayjs(start).add(i, "day");
    columns.push(date);
  }

  const filledColumns = fillEmptyColumns(columns, start, end);
  return filledColumns;
};

now 현재 시간을 토대로, now가 포함된 month을 기준으로 첫 번째 날짜 start를 가져왔고, 마지막 날짜인 end를 가져왔습니다.

가져온 날짜를 배열에 넣기 위해 길이를 구해줍니다. 날짜가 1부터 시작하므로 마지막날짜가 길이가됩니다.

end의 get("date") 를 통해 31 을 가져옵니다.

columns 라는 배열을 생성했고, 반복문을 통해 담아줍니다. endDate 만큼 반복합니다.

dayjs 에서 start 날짜를 기준으로 i 가 오는만큼 날짜를 더해서 columns에 담아줍니다.


첫 날 이전 공백 채우기

10월 1일이 일요일이기때문에 채워줄 필요는 없습니다.
하지만 다른 달에 했을때는 다르기 때문에 코드는 작성해주겠습니다.

// 1. 첫날 이전 공백 채우기
  const startDay = dayjs(start).get("day"); //0
  for (let i = 1; i <= startDay; i += 1) {
    const date = dayjs(start).subtract(i, "day");
    filledColumns.unshift(date);
  }

start 시작일의 요일에따라 반복문이 몇번돌지 정해줍니다.
일요일인 경우에는 day0 이기때문에 0만큼 채워줍니다.
수요일인 경우에는 day3 이기 떄문에 3만큼 채워줍니다.

start 를 기준으로 i 만큼 뺀 날짜를
unshift 함수를 이용해서 차례대로 앞에 넣어줍니다.


마지막날 이후 공백 채우기

 // 2. 마지막날 이후 공백 채우기
  const endDay = dayjs(end).get("day");
  /**
    0 -> 6
    1 -> 5
    2 -> 4
    endDay + ? = 6
   */
  for (let i = 1; i <= 6 - endDay; i += 1) {
    const date = dayjs(end).add(i, "day");
    filledColumns.push(date);
  }

end 마지막 날의 요일을 기준으로 반복문에 몇 번 돌지 정해줍니다.
push 함수를 사용해서 마지막에 차례대로 넣어줍니다.

이렇게 해서 날짜 데이터를 columns 배열에 담아주는 getCalendarColumns 함수를 살펴보았습니다.


달력 그리기

만들어진 columns 를 가지고 달력을 그려줍니다.

안쓰는 부분은 지워주었습니다.

FlatList를 선언하고, date에는 columns, renderItem 을 추가해줍니다.

renderItem 은 가독성있게 return문 위 쪽에 작성해주었습니다.

prop 안의 item으로 데이터의 컬럼에 하나하나 접근해줍니다.

우선 Text 로 만들어주었고, 안에 들어갈 itemdate이기 때문에 재명명 해주었습니다.

dayjs로 감싸주면 dayjs의 함수를 사용할 수 있습니다.
dayjs를 통해 date를 가져와줍니다.

35개의 원소가 담긴 배열인 columns가 랜더링되었습니다.


제 경우에는 잘리지 않았지만, 다른 환경에서는 잘릴 수 있으니
SafeAreaView 로 수정해서 안전한 영역에서 표시되도록 수정해주었습니다.


달력은 한 줄에 7개의 요일이 들어갑니다.

numColumns : 한 줄에 몇개의 columns이 그려질지 결정해주는 속성

한 줄에 7개씩 렌더링된 모습입니다.

스타일링을 주기위해 View 를 사용해주었고, Text들 사이에 공백을 추가해주었습니다.

달력 같아진 모습!! 상단바 영역을 침범하는 건 불편하지만 추후에 전체적인 스타일링 하면서 조정해줄테니 일단 넘어갑니다.. ㅎ

중앙에 오도록 스타일링을 한 번 더 추가해주었습니다.

요일별 색상넣기

공백은 이제 필요 없으니 지워주었습니다.

dateday 에 따라 색상을 다르게 해줍니다.
dayjsdate를 감싸주고, get('day')로 요일 정보를 받아옵니다.

day0(일) 일때와 6(토)일 떄 예외처리를 해줍니다.
삼항연산자를 사용하여 구현해주었습니다.

색상이 들어가니 점점 형태가 나오네요.

현재 월이 아닌 경우 Opacity 처리

마찬가지로 TextOpcity를 설정해주고,
dayjs를 활용해 datenowmonth 와 같은지 비교하여 isCurrentMonth 값을 전해줍니다.

작성일 10월 24일입니다. 다음 달인 11월은 Opacity 처리 되어 불투명해졌습니다.

요일그리기

날짜가 랜더링된 부분 위에 들어갈 것이기 때문에 ListHeaderComponent로 만들어줄 수 있습니다.

요일을 그리는 부분이 날짜를 그린 부분과 유사하여 요일에도 똑같이 적용이 가능합니다.

renderItem 부분을 Column컴포넌트로 따로 빼주기 전에!

0부터 6까지의 param을 받아 요일을 반환하는 getDayText 함수는 src폴더의 utill.js에 선언해주었습니다.

case문을 통해서 리턴해주어도 되고, 배열을 생성하고 day가 곧 index이므로 그대로 리턴해주어도 됩니다.


그리고 color 부분도 마찬가지로 0부터 6까지의 param을 받아 색상을 반환하는 getDayColor 함수도 src폴더의 utill.js에 선언해주었습니다.


다시 App.js 로 돌아와서 renderItem 부분을 Column 컴포넌트로 빼주겠습니다.

text, color, opacity 를 받아서 return 해주는 Column 컴포넌트를 만들어주었습니다.

현재 날짜와, 요일을 출력해줄 헤더부분인 ListHeaderComponent를 추가해주었고, 요일부분만 우선 작성하였습니다.

flexDirection: "row"로 스타일을 주어 요일이 가로로 배치되도록 했습니다.

renderItem 부분도 기존 코드에서 Column 컴포넌트를 재사용하도록 수정하였습니다.

최종 return 부분에도 ListHeaderComponent를 추가해주었습니다.

요일이 위에 잘 있겠지만,,,,,,,,,, 미뤄뒀던 상단바 영역 구분을 해 주어야 할 것 같습니다.


상단바 영역 제외

react-native-iphone-x-helper 라이브러리를 받아줍니다.

> yarn add react-native-iphone-x-helper

getStatusBarHeight, getBottomSpaceimport 해주고, statusBarHeight, bottomSpace에 해당 함수를 이용해 상단바 영역과 하단 영역의 높이를 넣어주었습니다.

statusBarHeight에는 (true) 를 주어 안전영역을 포함한 높이로 넣어주었습니다.

App.js 가장 하단에 있는 StylesheetpaddingTop : statusBarHeigght 로 지정해주었습니다.

요일도 잘 보이는 모습입니다!


현재 날짜 표시

currentDateText로 현재 now 날짜를 받아와 포맷해주고, 스타일링을 해주었습니다.

Margin 컴포넌트 추가

Margin은 유용하게 많이 사용될테니 src 폴더에 `Margin.js를 추가하여 따로 작성해주었습니다.

날짜 크기가 너무 작아서 Text 스타일링을 해 주었고, 위 아래로 Margin을 추가하였습니다.


월 이동 버튼

https://icons.expo.fyi/Index
항상 애용하던 사이트에서 아이콘을 가져와줍니다.

SimpleLineIconsarrow-leftarrow-right를 선택해주었습니다.

당장 사용해야할 버튼도 2개이기 때문에 ArrowButton 컴포넌트로 따로 만들어주었습니다.

터치가 가능한 버튼이어야하기 때문에 TouchableOpacity로 감싸주고, 스타일링 해주었습니다.

ListHeaderComponent에 적용해줍니다.

버튼 스타일에 Padding을 넣어주었기 때문에 Margin을 제거해주었습니다.

UI만 우선 구성하고, 함수부분은 아직 비워두었습니다.

UI가 모두 구성된 모습입니다.


https://velog.io/@bi-sz/투두리스트프로젝트[2]

기능구현은 다음 게시글에서 이어서 진행하겠습니다.

0개의 댓글