Diary App
0️⃣
data.json
파일
- 이 mock data 파일은
npx create-react-app
으로 생성된 리액트 폴더의 root에 저장해야 한다. (src
폴더 밖에 ) - 해당 파일을 읽기 위해
-
npm i json-server --save-dev
을 통해 json-server을 설치하고 -
npx json-server --watch data.json --port port#
을 통해 json-server을 실행하여 읽을 data 파일과 서버 포트 번호를 지정해준다.
-
1 | { |
여기서 posts는 /posts
url을 의미하고, data는 그 페이지 안에 들어있는 data 묶음을 의미한다.
data 안에는 2021년에 대한 data 배열이 있는데, 이를 자세히 보면
1 | "2021": [ |
이렇게 12개의 배열이 담겨있다. 즉, 월별 데이터가 각 배열에 들어있다는 의미.
1️⃣
component 구조

-
/components
Main 페이지를 보면, 크게
1 | ├ MainHeader |
이렇게 나뉜다.
여기서 공통적으로 적용되는 부분은
1 | /common |
이다.
이 외에 페이지 (Main / Diary) 에 따라 달라지는 부분은
1 | /main |
가 있을 것이다.
-
pages
위의 컴포넌트를 모두 적용시킨 페이지의 경우 따로 폴더를 만들어 페이지별로 컴포넌트를 생성한다.
1 | /pages |
- 그 외…
-
assets
lib
2️⃣
App.js
1 | import React from "react"; |
root ( App.js
) 에서 부를 모든 컴포넌트 import 하기
데이터 이동이 있는 등 리렌더링이 진행되는 컴포넌트는 모두 BrowserRouter
에 넣는다. main, diary 페이지가 모두 사용하는 MainHeader
, Calendar
, Title
가 이에 해당하며, 특히 컴포넌트 클릭시 url이 바뀌고 렌더링 되는 컴포넌트가 달라져야 하는 경우 Switch
안에 넣는다. Switch 내에는 Route
로 url 별 컴포넌트 렌더링을 달리 정의한다.
여기서 main은 path="/"
일 때이므로 exact를 설정해 Main을 넘겨주고, /diary
일 때는 Diary 컴포넌트를, 그 외에는 Page Not Found
를 띄워준다. Route
는 위에서부터 차례로 내려가며 path를 비교해 처음으로 매칭되는 컴포넌트를 렌더링한다!
1 | // BrowserRouter 내부에 모두 넣어줍시다 |
exact에 대한 자세한 설명은 다음을 참고하자.
For example, imagine we had a
Users
component that displayed a list of users. We also have aCreateUser
component that is used to create users. The url forCreateUsers
should be nested underUsers
. So our setup could look something like this:
1
2
3
4 <Switch>
<Route path="/users" component={Users} />
<Route path="/users/create" component={CreateUser} />
</Switch>Now the problem here, when we go to
http://app.com/users
the router will go through all of our defined routes and return the FIRST match it finds. So in this case, it would find theUsers
route first and then return it. All good.But, if we went to
http://app.com/users/create
, it would again go through all of our defined routes and return the FIRST match it finds. React router does partial matching, so/users
partially matches/users/create
, so it would incorrectly return theUsers
route again!The
exact
param disables the partial matching for a route and makes sure that it only returns the route if the path is an EXACT match to the current url.So in this case, we should add
exact
to ourUsers
route so that it will only match on/users
:
1
2
3
4 <Switch>
<Route exact path="/users" component={Users} />
<Route path="/users/create" component={CreateUser} />
</Switch>
3️⃣
공통 컴포넌트 update!
MainHeader.js
Router Props
브라우저와 리액트앱의 라우터를 연결하게 되면 그 결과 라우터가 history api에 접근할 수 있게 되며 각각의 Route와 연결된 컴포넌트에 props로 match, location, history라는 객체를 전달하게 된다.
![]()
Match
match 객체에는
와 URL이 매칭된 대한 정보가 담겨져있다. 대표적으로 match.params로 path에 설정한 파라미터값을 가져올 수 있다.![]()
Location
location 객체에는 현재 페이지의 정보를 가지고 있다. 대표적으로 location.search로 현재 url의 쿼리 스트링을 가져올 수 있다.
![]()
👆 요청 url
![]()
History
history 객체는 브라우저의 history와 유사하다. 스택(stack)에 현재까지 이동한 url 경로들이 담겨있는 형태로 주소를 임의로 변경하거나 되돌아갈 수 있도록 해준다.
![]()
withRouter
을 이용해 컴포넌트를 감싸면 컴포넌트 자체에서도 location
, history
와 같이 url을 활용할 수 있도록 한다.
1 | // Route가 아닌 요소에서 location을 사용하기 위해서 withRouter를 사용합니다 |
props에 history
을 받아와 컴포넌트 간 이동을 컨트롤한다. history.push()
를 통해 url 이동이 가능하다.
1 | const MainHeader = ({ history }) => { |
-
Title.js
1 | const Title = ({ location }) => { |
-
Footer.js
1 | const Footer = () => { |
©
는 Copyright 표시인 © 를 보여주는 html 요소이다 👾
Calendar.js
Calendar 컴포넌트 및 다른 컴포넌트에도 공통된 state를 내려주기 위해 App.js에서 공통된 state를 생성 및 설정하고, props로 내려준다.
App.js에서 년도와 월을 저장한 후, 데이터를 Calendar.js로 전달해주는 방식입니다!
App.js
1 | import React, { useState } from 'react'; // state를 사용하기 위해 useState를 불러옵니다 |
Calendar.js
App.js에서 넘겨준 props를 받아준다.
1 | const Calendar = ({ currYear, setCurrYear, currMonth, setCurrMonth }) => { |
useRef는 DOM 요소를 조작하기 위해 생성하는 상응하는 리액트 내 DOM 객체이다.
1 | // useRef를 사용하여 "DOM 요소를 가져올 수 있는 변수"를 선언합니다 |
1 | const isMain = location.pathname === "/" ? true : false; |
1 | <img |
onClick
시에는 year를 다시 세팅해야 하므로 props로 받은 setCurrYear 함수를 이용해 year를 알맞게 설정한다. 이 때, url이 “/“ 인 경우에만 월 이동이 가능하게 설정하기 위해 isMain &&
조건을 적용한다.
onMouseEnter
과 onMouseLeave
의 경우에는 마우스를 DOM 요소에 호버 온/오프 했을 때의 변화를 설정하는 것이므로, ref를 전달하고 해당 ref의 current.src
에 알맞은 svg
을 전달하여 알맞은 아이콘을 렌더링하도록 한다.
1 | <div className="calendar__month"> |
map
함수를 사용할 시, 되도록 key
props를 설정하도록 습관을 들이자! 또한, map 사용 시 꼭 return
문을 사용해야하는 것은 아니지만, 만약 사용한다면 {}
로 return 문을 감싸야 한다!
여기서는 월을 선택했을 때 currMonth
을 다시 세팅하고, 그에 맞는 스타일을 적용해야 하므로 style 객체에 조건문을 설정하여 알맞게 렌더링되도록 한다. 여기서 jsx 의 특성을 엿볼 수 있는데, 일단 javascript 문법이 들어가기 때문에 style={}
로 문법임을 선언해주고, 이 안에 조건문을 설정한 후 실제 스타일 객체는 {}
로 전달해준다!
4️⃣
Card 설정
Main.js
Main 페이지는 Card를 포함하는 최상단 페이지이므로, 자식 컴포넌트로 Card를 렌더링하고 props로 userData를 전달한다.
1 | return ( |
Card.js
1 | // 서버에 date가 20200509 형식으로 저장되어있기 때문에, 이를 "5월 9일" 형태로 반환하는 함수입니다 |
props로 받은 userData
객체를 구조분해할당하여 개별적인 변수에 담는다.
1 | const Card = ({ userData }) => { |
1 | // text 길이가 길어지면 ... 로 생략하는 방법(아래의 3가지 속성을 세트로 사용합니다) |
5️⃣
API 설정
Main 컴포넌트 자체만 넘기지 않고 props도 함께 전달하기 위해 Route 내 component props을 변경한다.
1 | // Main 컴포넌트에서는 "해당 월의 데이터만" 가지고 있어야하므로 Main에 현재 연도와 월을 넘겨줍니다 |
GetUserData API를 작성하여 json-server에 있는 data를 가져옵니다. 실제로 가져오기 전에 Postman으로 테스트 해보면 좋겠죠?
api 요청 함수는 무조건 async
, await
으로 작성하여 비동기 처리한다! 데이터를 받지 않으면 진행되지 않아야 버그를 방지한다.
get url 의 경우, 앞서 data.json
에서 설명했듯이 설정한 서버 포트 넘버 3001번에서 /posts
url 에서 data를 가져온다. 이 때, api로 가져오는 data는 객체 내에 data에 대한 metadata와 함께 data를 가져오므로 기본적으로 다음과 같은 구조를 가지고 있다.
1 | rawData { |
여기서는 data
라고 명명하였으므로, data 내 data에 접근하여 data를 가져온다.
1 | export const getCardData = async () => { |
App.js에서 내려준 year
, month
를 props로 받아 data의 property로서 접근하여 알맞은 데이터를 가져온다. api에서 data를 받아올 것이므로 초기값은 null
로 설정하고, useEffect
를 이용해 setState한다. 이 때, useEffect 에 전달하는 callback 함수의 경우 밖에서 정의 후 전달하는 것이 아닌 함수 내에서 정의하는 것이므로, IFFY 를 위해 기본적인 함수 생성문 () => {}
정의 후 내부에 IFFY를 위한 (() => {})()
를 정의한다. 이 때, api를 이용해 data를 호출하므로 async, await로 data를 받아오고 설정해야 한다. (api 함수 자체가 async, await 함수이므로) 또한, year과 month가 바뀔 때에만 리렌더링을 해야하므로 dependency 배열에 이 두 변수를 설정한다. 즉, dependencies가 year, month 이므로 year와 month가 변경되면 api를 다시 요청하게 된다.
1 | const Main = ({ year, month }) => { |
Reference
Router : https://gongbu-ing.tistory.com/45