yja
[Offnal] 교대근무 앱을 만들며.. 개발 회고 본문
테이브 연합 동아리에서 시작해서 지금까지 연장해서 개발 중이던 오프날 프로젝트가 이제 출시를 앞두고 있다.
테이브가 7월에 끝났으니까 추가로 4개월 정도 더 개발했다. 테이브에서 오프날 프로젝트로 우수상까지 받고 더 디벨롭하지 않으면 조금 아쉬울 것 같아 전체적으로 리펙토링도 하고 기능적인 부분도 완성도를 높여가기로 하였다.
동아리 활동 이후 연장한 프로젝트의 진행 과정을 쭉 돌아보려고 한다!
프로젝트 소개
프로젝트 목적
교대 근무하는 사람들은 하루하루마다 자신의 근무형태가 바뀌기 때문에 스케줄 관리가 힘들다.
스케줄이 야간, 주간 처럼 바뀌어서 어느 날은 몇 시간 자지 못하고 출근할 때도 많다. 그렇기 때문에 수면 패턴이나 식습관도 규칙적으로 잡히기는 힘들기 때문에 이런 교대근무자들을 위해 스케줄과 건강 관리 등의 기능을 한번에 관리할 수 있는 앱이 교대근무자들의 불편함을 해소해준다.
교대근무는 팀으로 이루어져 팀 내의 다른 조의 스케줄을 확인할 필요도 있다. 나의 스케줄뿐 아니라 단체(팀) 스케줄도 한번에 확인이 가능하다.
프로젝트 개요
- 프로젝트 유형: 동아리 팀 프로젝트
- 앱 주제: 교대근무 일정 관리 앱
- 사용 기술: React Native, Zustand, Tailwind Css, Sqlite
- 내 역할: 프론트엔드 개발
- 팀 내 파트 구성: 프론트엔드, 벡엔드, 딥러닝, 디자이너
맡았던 역할
- 화면 구조 설계
- 상태 관리 구조 고민
- 캘린더/시간 선택 UI 등
- 개인/팀 근무표 API 연동
- 헬스 어플리케이션 연동
처음 해본 React Native
React는 알고 있었지만 React Native라는 존재 정도만 알고 있었어서 동아리에서 프로젝트를 시작하게 되면서 접하게 되었다.
처음부터 사람들이 몰렸던 프로젝트라 무지했던 RN이지만 할 수 있다고 말만 해놓은 상태로 바로 RN 인강을 샀다.
React와 구조와 문법이 비슷하기도 하지만 빌드 과정과 네비게이션, param와 같은 새로운 개념들도 많았다.
처음엔 RN을 설치하고 관련 앱인 Xcode와 android Studio를 설치하고 실행시키는 것부터가 큰 난관이었던 것 같다.
그동안의 프로젝트는 웹사이트만 개발해보았고 주변에도 RN으로 개발해봤다는 친구가 없었고, 나에게도 앱을 개발할 일은 없을 거라고 생각했는데 이번 기회에 배우게 되면서 새로운 분야를 배운다는 느낌도 받았다.
그리고 또 앱의 좀 더 예쁜 UI와 네비게이션이 신기하고 좋았다.
어려웠던 문제
1. 근무표에서 조직의 개념을 이해하는데에 오래걸렸다.
하나의 조직(단체)에 하나의 근무표가 등록된다.
처음에 개인 근무표를 만들었더라도 단체 근무표 수정을 통해 단체 근무표가 될 수 있다.
이미 근무표가 저장된 달에는 다른 조직으로 그 기간에 근무표를 저장할 수 없다.
조직은 근무표 저장 요청을 할때 동시에 조직이 생성되어야 한다.
- 근무표 조회
- 한 사람이 여러 조직을 생성할 수 있다. 따라서 벡엔드가 생성한 시기대로 정렬해서 조직을 보내주면, 프론트에서는 '나의 조'와 '마지막 조직이름'을 기준으로 저장된 근무표 조회를 시도한다.
- 개인/단체 근무표 조회: 근무표 조회할 때 마지막 조직 기준으로 조회하기 위해 latestOrganization 정보를 전역 상태로 빼서 여러 컴포넌트에서 조회할 수 있다.
- 앞으로 생각해봐야할 것:
- 마지막 조직을 기준으로 조회되는데, 그러면 사용자가 이전의 다른 조직의 근무표를 다시 확인할 길이 없다. API 요청으로는 조직이름을 통해 조회할 수 있지만, 사용자가 자신이 만든 조직들을 알고 어떤 조직의 근무표를 조회할건지 선택하는 로직이 있어야한다.
- 이미 근무표가 있는 날짜에 같은 조 & 다른 조직으로 등록 시도를 하면 등록이 되지 않는다. 이 경우를 위해 아예 사용자에게 어떤 경고를 하던지 아니면 이미 등록되어있는 달/날짜의 UI를 비활성화해서 등록하지 못하도록 막아야 할 것 같다.
- 근무표 저장
- 팀마다 같은 날짜에 같은 근무타입이 나올 수 있다. 사용자별로 단체 근무표가 하나씩 있는데, 공유하는 게 아니라 반드시 교대 스케줄이 맞지 않아도 된다. 교대 근무라면 근무시간이 겹치면 안되지만 이건 실제 근무표가 아니므로 사용자가 이를 직접 설정하여 수정 가능해야한다.
- 근무표 수정모드에서 처음 등록했던 캘린더 범위에 들어가 있어야만 그 근무일을 수정하는 것이 가능했다. 이건 기획의 문제여서 헷갈렸는데, 근무표가 이미 있는 상황에서 캘린더 범위에 벗어나더라도 사용자 입장에서는 다른 달의 근무표를 연장해서 저장시킬 수 있다고 생각할 것이므로 근무표 수정 모드 화면에서도 가능해야한다. 따라서 논의를 통해 캘린더 범위를 받지 않고 기존 범위에 벗어나면 등록되는 것으로 수정되었다.
2. Map과 Record 데이터 구조 선택
리펙토링 이전에는 날짜별 근무형태 정보가 있는 calendarData를 Map 구조로 선택했다.
그때는 몇달 전이라 왜 Map으로 선택했는지는 잘 기억이 안나지만 아마도 어떤 데이터 구조들이 있는지 잘 알아보지 않고 선택했던 것 같다. 동아리가 끝난 후 전체적으로 리펙토링에 들어가면서 프론트 측에서 접근하기 훨씬 쉽고 다루기 쉬운 데이터 구조가 Record라는 것을 깨달았다.
어쩐지 리펙토링 전에는 Map 구조였기 때문에 서버로 보낸 데이터가 콘솔에서 확인해보면 항상 '{}'로 나와서 정확한 데이터를 확인할 수 없다고 나왔던 것이다.
Map vs. Record 비교하기 ▼
개인 캘린더 데이터

리펙토링 전 -- Map 구조
const calendarData = new Map<string, ShiftType>([
["2025-09-01", { workTypeName: "주간" }],
["2025-09-02", { workTypeName: "휴일" }],
]);
// 접근 방법:
calendarData.get("2025-09-01"); // { workTypeName: "E" }
calendarData.has("2025-09-03"); // false
저장 방법:
React는 값을 비교할 때 참조기준으로 판단하는데, Map의 `set()`함수로 저장하면 같은 Map을 수정하게 된다. 따라서 Map을 new로 새로 생성하지 않으면 이전 값과 같다고 판단하기 때문에 매번 new로 선언해줘야한다.
setCalendarData(prev => {
const next = new Map(prev);
next.set(date, { workTypeName: "E" });
return next;
});
리펙토링 후 -- Record 구조
const calendarData: DateAndWorkTypeRecord = {
"2025-09-01": { workTypeName: "오후" },
"2025-09-02": { workTypeName: "휴일" },
}
// 접근 방법:
const day = calendarData["2025-09-01"]; // { workTypeName: "오후" }
const shift = day.workTypeName // "오후"
저장 방법:
키가 없으면 새로 생성하고 이미 키가 있으면 덮어쓰게 된다.
Map 과는 다르게 Object는 값을 변경하면 항상 새 객체를 생성해서 React의 참조 비교에도 달라졌다고 알아차린다. 다만 `...`스프레드 연산자를 사용해야 기존 값을 그대로 유지한채로 추가가 되며, 한 값만 변경시키고자 스프레드 연산자를 사용하지 않으면 기존 값이 사라지고 완전히 새로운 값 하나가 들어가게 된다.
setCalendarData(prev => ({
...prev,
[date]: { workTypeName: "E" },
}));
<Map보다 Record가 더 좋은 이유>
1) 데이터 접근성
캘린더 데이터는 프론트에서 데이터를 접근하기 쉬운 형태인 레코드 형식으로 구현하기로 하였다.
레코드 형식으로 데이터를 구성하면 쉽게 데이터에 접근할 수 있다.
날짜 → 데이터가 1:1로 대응되는 도메인에서는 직렬화가 되는 Record(해시 맵) 구조가 가장 적절하다.
2) 직렬화가 가능하여 바로 네트워크 통신 가능
Record는 Map과 달리 데이터 직렬화가 가능하다.
직렬화 = 메모리에 있는 구조적인 데이터를 문자열/바이트 같은 “전송 가능한 형태”로 바꾸는 것 !!
- Map 직렬화 시도 : 직렬화가 되지 않아 {} 로 보인다. JSON은 key-value를 모르고 빈 객체로 처리한다.
JSON.stringify(new Map([["a", 1]]));
// "{}"
- Record 직렬화 시도
JSON.stringify({
"2025-07-01": "E"
});
// OK // {"2025-07-01":"E"}

네트워크를 할때는 바이트(문자열)만 전송 가능한데, 이 공통 언어가 Json이다. Record는 Json의 허용타입에서 object에 해당한다.
Map은 배열도 아니고, 객체도 아니고, JS 엔진 내부 자료구조이다.
“JSON 객체와 1:1로 대응되는 구조”이기 때문에 Record가 서버 전송에 좋다는 것이다.
따라서 Map 형태이면 직렬화가 가능하게 Record로 변환이 필요하다.
// Map → Record
Object.fromEntries(calendarData)
3. 서버 타입과 앱 내부에서 쓰는 타입을 분리하기
처음엔 서버 타입과 앱 내부에서 쓰는 도메인 타입이 거의 동일하지 않은가?라고 생각했는데 아닌 경우가 있었다.
프론트엔드에서는 시작시간과 끝나는 시간을 가지고 화면에 표시하고, 이걸 그대로 서버로 보내면 된다고 생각했지만 서버 측에서는 duration 을 요구했다. 벡엔드에서 시간을 관리할 때는 오늘 시간인지, 넘어간 내일 시간인지 정확히 구분하려면 시작시간과 duration을 알아야한다고 한다. 그래서 도메인으로는 시작 시간과 끝 시간을 그대로 쓰고, 데이터를 전송할때는 서버 타입에 맞게 duration으로 변경해서 보내주었다.
4. zustand와 context API의 차이가 무엇일까?
context와 zunstand store는 기능적으로는 같은 역할을 할 수 있지만 zustand는 상태가 변경되어도 그 상태를 참조하는 모든 컴포넌트에서 리렌더링이 발생하지 않는다. selector을 통해 선택한 상태가 변경되고 그 상태를 구독한 컴포넌트에만 리렌더링이 발생한다.
하지만 context API는 상태가 변경됨에 따라 그 상태를 참조하는 모든 컴포넌트에서 리렌더링이 발생한다. (Provider의 value가 변경되면, 그 하위 컴포넌트들에게서 리렌더링이 발생한다. )
5. OCR 관련 데이터 연동 이해하기
이건 내가 개발하지 않아서 아직 완벽히 이해하진 못한 것 같다.
맨 처음에는 OCR 모델을 프론트에서 직접 사용했는데 이 경우에 사진 인식 정확도가 떨어져서 온디바이스 방식 대신, 벡엔드의 AI 서버와 연동하기로 바꿨다. 벡엔드의 AI 서버는 딥러닝 팀의 OCR 모델을 사용한다.
OCR 인식 플로우 --
사용자가 카메라 촬영 or 이미지 업로드 -> OCR API 요청 (벡엔드의 AI 서버 호출) -> 결과 응답을 프론트에서 받고 표시한 후 -> 사용자가 잘못 입력된 부분을 수정 -> '다음'을 눌러 서버로 최종 근무표 저장 요청
버그 해결하기
1. 스플래시 스크린에서 무한 로딩 문제
앱 시작 화면 -> 로그인 화면 -> 홈화면 순으로 플로우가 진행된다.
만약 최근에 로그인을 했었다면 기존 로그인 정보를 가지고
앱 시작 화면 -> 홈화면 으로 바로 이동하게 된다.
최근에 로그인을 했다면 바로 홈화면으로 이동했지만 로그인을 오랬동안 안한 경우에
앱 시작 화면(스플래시 스크린)에서 동작이 멈추는 상황이 발생했다.
개발자 도구에서도 이 과정에서 API 오류가 뜨지 않았고, 그냥 화면이 멈추는 상황이 발생했다.
앱 시작화면에서 토큰이 만료되면 토큰 재발급 API가 실행되어야 하는데 이 함수가 실행되지 않는 것 같았다. 근데 로그에는 아무런 오류가 뜨지 않아서 뭐가 문제인지 찾는데 오래걸렸다.
문제의 원인은 토큰 재발급 API가 이미 토큰 재발급 기능을 인터셉터로 갖고 있는 API instance를 기반으로 이루어지고 있어서 중복 API 요청이 이루어져 axios의 API 요청 자체가 막혀버린 것이다.
그래서 앱 시작 화면에서 홈화면으로 넘어갈 때 요청하는 토큰 재발급 API는 인터셉터를 갖지 않는 API instance를 따로 만들어 요청했더니 해결되었다.
코드 보고 이해하기 ▼
토큰 재발급 인터셉터가 없는 api 인스턴스를 통한 호출 함수를 만든다.
헬퍼 함수를 통해 axios instance를 매개변수로 받아 어떤 api 인스턴스를 통하는지 보기 쉽게 하였다.
// 헬퍼 함수
private tokenReissueHelper = async (
axiosInstance: typeof api,
refreshToken: string,
instanceName: string
) => {
try {
const response = await axiosInstance.post('/tokens/reissue', {
refreshToken,
})
// ..
}
// 인터셉터 있는 api instance를 통한 토큰 재발급
tokenReissue = async (refreshToken: string) => {
return this.tokenReissueHelper(api, refreshToken, 'with interceptor')
}
// 인터셉터 없는 api instance를 통한 토큰 재발급
tokenReissueWithNoInterceptor = async (refreshToken: string) => {
return this.tokenReissueHelper(
noInterceptorApi, // api instance
refreshToken,
'no interceptor'
)
}
2. 캘린더 날짜 밀림 스타일 문제
시뮬레이터에서는 개발자가 주로 정하고 있는 디바이스로 시뮬레이션을 해보기 때문에 이런 문제가 발생하는 지 몰랐는데,
앱 내부 테스트에 올려놓고 다른 팀원이 직접 앱을 스토어에서 다운받고 테스트 해보았을 때 디바이스 종류에 따라 날짜 밀림 현상이 발생했다.

처음에는 캘린더 외부에 얇은 border을 설정했더니 문제가 해결되었다. 외부에 테두리를 설정하면 계산상으로 레이아웃이 명확해지고, 설정하지 않으면 내부 요소에 따라 공간이 정해지기 때문에 그렇다.
그런데 이렇게 해결했더니 또 다른 디바이스에서 여전히 밀림 문제가 발생했다.
테두리를 설정하는 것만으로는 근본적인 문제를 해결할 수 없었다.
캘린더 UI를 만들 때 7일씩 단을 내려서 만드려고 날짜를 flex-wrap 으로 나열하고 넘치는 건 아래로 내려가도록 했다. 디바이스 크기가 달라지면 그에 따라서 계산 비율이 달라져 생긴 문제였다.
그래서 flex-wrap 나열을 버리고, 2차원 격자 배열을 만들어서 스타일이 밀리지 않게 해결했다.
이번 달을 ‘주 단위 행(row)’로 쪼개고, 각 주는 항상 7칸을 가진 flex-row로 직접 채운다
격자 배열 코드 이해하기 ▼
예를 들어 2025년 7월 달력을 보자.
달의 첫날 앞 빈칸(2) + 총 일자 수(31) = 총 슬롯 수(33)
총 슬롯 수(33) / 1주(7) 에 반올림 = 행 수(5)
한 주의 일자 수 = 열 수(7)

아래 코드처럼 for문을 통해 나열하면 숫자가 밀릴 일이 없다.
for (let row = 0; row < totalRows; row++) { // 행 (n주)
for (let col = 0; col < 7; col++) { // 열 (n요일)
// 날짜 나열 ...
}
}
아숴웠던 점, 다음에 한다면 ?
- 초기에 상태 구조를 너무 빨리 확정했던 점. 이건 리펙토링 하며 데이터 구조를 다시 생각해보면서 개선했다.
- 공통 컴포넌트를 미리 상의해보지 않았던 점
- 내부 DB의 장점에 대해 다 이해하지 못한 것 같다. 빠른 조회와 오프라인 조회를 할 수 있다는 건 좋은데, 그럼 왜 어떤 데이터는 사용하고 어떤 데이터는 사용하지 않을까.. ? 그렇다면 캘린더 데이터도 할 수 있다면 좋은걸까 .. 솔직히 내부 DB를 쓰기에는 조금 간단한 기능이지 않나라는 생각도 든다.
- 챗지피티에게 내가 했던 질문들을 바탕으로 정리해달라고 했을 때 UX에 관한 질문이 거의 없었다고 했다. 생각해보니 플로우를 먼저 생각하고 개발하지 않았던 것 같다. UI를 구현해놓고 나서 보니 그런 UI 정보가 어디서 필요한거지를 생각해보지 못하고 바로 디자이너에게 이건 어떻게 쓰이는지 자세히 물어보지 않았다. 다음에는 기능 구현 전에 데이터 흐름이 어떻게 되는지 점검해봐야겠다
앞으로 해보고 싶은것
- 서버 상태를 바로 적용하는 리액트 쿼리 사용해보기
- zustand의 미들웨어로 persist만 알고 있었는데 immer라는 미들웨어도 있다고 한다. 아직은 잘 모르지만 캘린더 데이터를 저장할 때 유용해보인다.
- 자동 카카오 로그인을 해보고 싶다. 시뮬레이터로 테스트 해볼 때도 항상 이메일과 비밀번호를 통해 동의를 눌러줘야하니 번거로웠다. 사용자들도 처음 가입할 때부터 귀찮음을 느낄 것 같다.
이 프로젝트를 통해 얻은 것
- React Native에 대한 실전 감각
- 이제 RN 이라면 정말 많이 알게됐다. 이걸 배우다보면 뭔가 리액트보다는 좀 더 재밌는 부분들이 있는 것 같다. 물론 빌드 오류가 오랫동안 해결되지 않을 땐 가끔 포기하고 싶어지기도 했다..
- 모바일 UX, UI에 대한 이해
- 모바일 디바이스는 종류가 많다보니 고려해야하는 UI 가 있는데 예를 들면 SafeArea나 화면 크기에 따른 동적인 크기의 UI 구현을 고려해야한다는 것을 알게되었다. 또한 모바일은 네비게이션이 있어 어떤 동작 다음에 어떤 데이터를 갖는 화면 또는 동작이 나와야하는지 잘 알아야했다.
- 선택 하나가 유지보수 비용으로 돌아온다는 경험
- 먼저 구현해보고 생각하기 보다는 처음부터 어떤 선택을 해야 최적일까를 고려해봐야한다는 걸 깨달았다.
- 위에서 말했 듯 주요 데이터인 캘린더 데이터 구조를 제대로 이해하지 못하고 Map으로 정했을 때 데이터에 접근하거나 서버로 변환 데이터를 만들 때 복잡하고 번거로웠다.
- 클린 아키텍처 구조
- 벡엔드와 비슷하게 repository/, data/, domain/ 같은 구조로 코드를 짜는 게 처음엔 너무 복잡하고 이해가 안되서 많이 공부해봤던 것 같다. 사실 지금도 완전히 이해했다고 느껴지진 않지만 이론으로만 배웠던 의존성 주입과 같은 개념을 프로젝트에서 적용해보며 이해할 수 있었다.
- 최근에 친구랑 다른 프로젝트를 할 때 이 구조를 설명해보고 적용해보려고 했는데 그 친구랑 이야기 해보았을 때 프론트에서 앞으로도 DB를 바꿀 필요가 없고 서버 DB만 사용할 거라면 굳이 repository를 통하면 복잡하기만 하고 번거로워 질 것 같아 이 폴더는 제외하기로 하였다. 앞으로 사용이 필요할 지 아닌지를 모르기 때문에 확장성을 고려해 모든 걸 지키고 가는게 맞는 걸까 고민이 되기도 하였지만, 사용될 지도 모르는 미래를 위해 모든 걸 지키는 구조를 선택할 필요는 없다고 생각했다.
- zustand 활용 기술
- 전역 상태 관리 라이브러리의 선택 기준은 무거운 리액트 네이티브 프로젝트에 부담이 가지 않도록 가벼울 것, 사용하기 어렵지 않을 것을 중점적으로 보았다. 많은 라이브러리들 중에서 zustand를 선택하기로 하였다.
- zustand의 persist 미들웨어를 사용하면 앱 로컬스토리지에 영구적으로 데이터를 저장시킬 수 있다는 것을 알게되면서 이 라이브러리가 가장 적합하다고 느꼈다. 앱을 삭제하거나 앱 데이터를 수동으로 삭제하지 않는 한, 재시작하는 경우 등은 상태를 유지할 수 있다. 이는 사용자 정보를 저장하고 토큰 재발급을 하는 경우 유용하게 쓰인다.
팀원에게 고마웠던 것
벡엔드 분들께 개발하면서 요청드린 수정사항도 많았는데 같이 고민하고 잘받아들여 주셔서 든든했다. 항상 열심히 개발하시고 대응해주시는 분들 .. 딥러닝 분들과는 직접적인 소통을 많이 하진 못했지만 항상 열심히 모델을 만드려고 노력 중이신 것 같다. 이 분야는 전혀 알지 못해서 어떻게 모델을 만드는지 그저 신기하고 대단하게 느껴질 뿐이다.
또 디자이너 분은 디자인이 정말 있는 앱 디자인처럼 전문적이고 예뻐서 프론트엔드로써 정갈한 UI를 짜고나서 보면 뿌듯해진다.
무엇보다 같이 프론트엔드를 맡았던 건우님께 정말 많은 걸 배웠다!
현업자이시다보니 현업에서 쓰이는 클린아키텍처 구조부터, sqlite, 앱 배포뿐 아니라 설계 문서를 작성하는 방법까지 배우게 되었다. 스스로 배우고 활용할 수 있도록 중간중간 과제도 내주시고 그 덕분에 빠른 기간에 많이 성장하게 된 것 같다.
이 회고 글까지도..
PM도 함께 맡으시느라 항상 고생많으십니다.. ❇️ 감사함니다 헤헤
그리구 프로젝트 연장하며 건우님이 데려와주신 멘토분들께도 여러모로 도움주셔서 감사합니다!
느낀 점
이렇게 동아리 활동에 연장해서 프로젝트를 깊게 해보고 출시까지 하게 되는 건 처음이라 신기하기도 하다.
테이브에서 이번 기수에 유독 연장하는 프로젝트들이 많다고 한다.
출시 예정일이 다가와서 자동 알람 기능은 뺐는데 출시도 하고 다른 새로운 기능들도 추가해보고 싶다.
유료화 기능을 한번 도전해보고 싶다. 돈 벌어야지
그저께 앱 스토어에 출시가 되었다고 한다! 🥹
그치만 조금씩 미완성인 부분이 있어서 계속 고치긴 해야한다.
https://apps.apple.com/kr/app/%EC%98%A4%ED%94%84%EB%82%A0-offnal/id6755481800
오프날 Offnal 앱 - App Store
App Store에서 Geonu Kim의 오프날 Offnal 앱을 다운로드하십시오. 스크린샷, 평가 및 리뷰, 사용자 팁, 오프날 Offnal 앱과 비슷한 다른 게임들도 만나볼 수 있습니다.
apps.apple.com
구글 플레이 콘솔에도 올린 상태인데 아직 테스터를 다 구하지 못해서 얼른 테스터도 구하고 심사도 되면 좋겠다. ㅎㅎ
휴학 기간동안 이런 알찬 프로젝트를 통해 많이 배우고 나름 바쁘게 걸어온 것 같아 정말 뿌듯하다!
'[App] React Native > Study' 카테고리의 다른 글
| [Offnal] 헬스 케어 어플리케이션 연동 (Apple Health Kit, Google Fit) (1) | 2025.10.21 |
|---|---|
| [Offnal] interceptor pattern을 통한 토큰 재발급 (0) | 2025.10.12 |
| [Offnal] tailwind 커스텀 유틸리티 클래스, 커스텀 폰트 적용 (0) | 2025.10.06 |
| [Offnal] Nesting Navigators 구성하고 접근하기 (3) | 2025.08.04 |
| 캘린더 구현하기: react-native-calenders (5) | 2025.07.04 |