2차 프로젝트 회고

 

프로젝트 소개 및 역할

우리 팀은 메가박스라는 영화 티켓팅 사이트를 클론 코딩하기로 정했다.  전체적인 기능은 백엔드쪽은 데이터 모델링 및 API 및 서버 구현하고 프론트 엔드 쪽은 메인 페이지, 로그인, 회원가입, 전체 영화 리스트, 영화 상세 페이지(댓글 기능 포함), 예매페이지, 상영시간표 페이지를  구현하였다.

프로젝트 기간은 9월 19일부터 9월 30일까지 2주이고 프론트 4명 백엔드 2명해서 6명이 한 팀이 되어 진행했고 1차 프로젝트와 동일하게 프론트엔드 쪽으로 참여를 하였다.  백엔드와의 통신은 API documentation을 보면서 VsCode에서 제공하는 LiveServer를 통하여 진행했다.

사용 기술

  • Front-end : React.js(개발환경은 CRU), sass,styled-componets, fetch,axios,,ContextAPI,webpack(컴파일러는  babel)
  • Common : RESTful API, PostMan, LiveServer
  • Community Tools : Slack, Zoom, Notion, Zepp, Trello
  • Version Control Tool : Git(flow는 github flow방식을 따름)

깃허브 링크

Front-end : https://github.com/cratuss/justcode-6-2nd-team2-front

Back-end : https://github.com/cratuss/justcode-6-2nd-team2-back

구현 기능

크게 예매페이지와 상영시간표 페이지의 UI와 기능 구현을 맡았다. 

 

1. 먼저 예매페이지 첫화면에서는 먼저 레이아웃을 잡는데 꽤 고생을 했고 가장 키포인트였다. 중점을 둔 부분은 슬라이드였다. 날짜를 화살표버튼을 누르면 넘어가게 하는 형태로 구성했으며, react-swiper라는 라이브러리를 통해 구현하였고, date-picker로 구현한 달력에서도 날짜를 구현하면 슬라이드에서 해당날짜로 이동하게 구현하였다. 이외에는 3개 이상 선택을 할 경우 분기점을 두고, 모달로 막아두었고 시간표에도 슬라이드를 구성하여 해당 시간대를 클릭하면, 해당 시간대 이후에 상영시간만 나오도록 프론트에서 자체적으로 filter를 구성하였다. 

두번째 화면에서는 좌석선택시 효과에 대해 중점을 두고 구현하였다. onMouseOver, onMouseLeave로 hover효과를 주었고 좌석 지정상태를 useState로 관리하여 오른쪽 결과창에 반영되도록 하였다.

마지막 세번째 화면에서는 실제로 결제 하는 부분으로서 iamport라는 외부 결제 라이브러리를 연동하여 카카오페이로 결제가 가능하도록 구현했다.

그리고 모든 컴포넌트에서 예매정보에 대해서 상태를 공유할 수 있도록 context로 관리하였다.


2. 위에 예매페이지와 겹치는 UI가 많았기에 비교적 수훨하였고 제일 중점을 두었던 부분은 백엔드에서 시간순서로 준 상영표를 관별로, 지역별로 다시 sort하여 구현하는 것이었다.

먼저 위에 네비게이션 박스는 받는 props로 렌더링되는 컴포넌트를 구분하였다. 또한 슬라이드는 동일하게 react-swiper를 사용하고, 실제 시간표를 hover하였을때, 영상시간과 현재 남은 좌석을 표시하고 클릭하면 해당 영화시간에 좌석페이지로 이동할 수 있도록 구현하였다. 

다음으로 관별, 지역별로 sort하여 표시해주는 것은 백엔드에서 받은 데이터를 filter와 foreach함수를 통하여 다시 순서를 맞추고 map을 돌렸다. 

 

아쉬웠던 점, 배운 점

아무래도 두번째 프로젝트였다 보니, 첫번재 프로젝트와는 달리 좀더 많은 react의 라이브러리나 api를 사용해보고 싶었으나 시간에 쫓겨서 사용하지 못했던게 아쉬웠다. 예를 들어 redux나 css 라이브러리인 boostrap을 들 수 있으며 다음 프로젝트에서는 꼭 쓸 수 있도록 해야겠다.

이외에 배운 점이라 한다면, 초기 세팅이 중요하다는 것을 많이 느꼈다. 첫 프로젝트는 초기 셋팅이나 컨벤션에 대해서 중구난방에 정확히 정해진게 많지 않았고, 그때 피드백으로 이번에는 처음부터 문법 규칙인 eslint, prettier파일을 설정하고 네이밍 규칙, 사용할 라이브러리를 정해놓고 시작하였다. 그러다보니 중간에 merge과정에서 꼬이거나 conflict가 나는 경우가 거의 없었다. 

 

백엔드와의 통신

첫프로젝트의 피드백으로 이번에는 db 모델링부터 함께 참여하면서, 통신에 있어서 의견이 부딪치는 부분을 최소화 시킬려고 많이 노력했다. 일단 이전 프로젝트 보다는 통신이 수월했고 api documentation도 내가 생각했던 방향으로, 받는 데이터가 구성되었다. 하지만 문제점은 내가 생각했던 방향이 틀렸다는 것이다. 초기 모델링때 내가 생각했던 데이터 통신과 구현하면서 바뀐 생각의 데이터 통신은 차이가 있었다. 구현하면서 추가적으로 필요한 데이터가 생겼고, sort해서 보내주는 부분에 있어서도 차이가 있었다.\결국 느낀 점은 다음 프로젝트부터는 스스로 db 모델링이 아닌 프론트쪽으로 어떤 흐름으로 구현될 것인지에 대해서 구체적으로 모델링을 해야되지 않나는 생각이 들었다.  

 

공유하고 싶은 코드

<Context.Provider value={{ scheduleId, setScheduleId }}>
      <ThemeProvider theme={theme}>
        <GlobalStyle />
        <BrowserRouter>
          <Header />
          <Routes>
            <Route path='/' element={<Main />} />
            <Route path='/Booking/*' element={<Booking />} />
            <Route path='/TimeTable/*' element={<TimeTable />} />
            {/* 영화페이지 */}
            <Route path='/movie' element={<Movie />}>
              <Route index element={<MovieContent />} />
              <Route path='now' element={<MovieContent />} />
              <Route path='upcoming' element={<MovieContent />} />
              <Route path='domestic' element={<MovieContent />} />
              <Route path='abroad' element={<MovieContent />} />
            </Route>

            {/* 영화 상세페이지 */}
            {/* <Route path='/moviedetail/1' end element={<MovieDetail />}> */}
            <Route path='/moviedetail/:id' end element={<MovieDetail />}>
              {/* <Route index element={<MovieInfo />} /> */}
              <Route path='movieinfo' index element={<MovieInfo />} />
              <Route path='comment' element={<MovieComment />} />
              <Route path='moviepost' element={<MoviePost />} />
              <Route path='trailers' element={<MovieTrailers />} />
            </Route>
            {/* <Route path='/theater' element={<ListTheater />} /> */}

            <Route path='/user-find' element={<UserFind />} />
            <Route path='/pass-find' element={<PassFind />} />
            <Route path='/signup' element={<SignUp />}>
              <Route path='consent' element={<Consent />} />
              <Route path='info' element={<Info />} />
              <Route path='complete' element={<Complete />} />
            </Route>
            <Route path='/cinema' element={<Cinema />} />
          </Routes>
          <Footer />
        </BrowserRouter>
      </ThemeProvider>
    </Context.Provider>

Context를 통해 팀원 모두가 공통 데이터와  Theme관련 색깔, 글씨 색깔, 크기, 등... css관련 룰을 정했던 부분이 이전 프로젝트와 가장 달라진 부분이였기에 공유하게 되었다.

마무리

개발자로서 두번째 프로젝트 였기에 좀 주니어 티를 벗어나서, 나름 컨벤션도 구체적으로 정하고, 다양한 라이브러리도 연동하여 사용했지만, 다시 돌아보면 아직도 부족한 부분 투성이였다. 

그리고 무엇보다 느낀점은 기능적인 부분보다 UI나 특히 레이아웃 구성에 있어서 배운점이 많았다. 첫프로젝트는 비교적 레이아웃이 단순했기에 그닥 크게 생각안했지만, 이번에는 레이아웃이 거의 코딩시간의 절반을 차지할정도로 복잡했고 시간에 쫓겼다. 

그렇기에 레이아웃에 대한 다양한 형태의 화면의 태그 구성에 대해서 좀 더 공부할 필요가 있다고 느끼면서, 어찌보면 프론트의 꽃은 다양한 반응형 웹, 화려함이라는 이면속에 기본을 구성하는 레이아웃이 아닐까 싶었다. 

 

1차 프로젝트 회고

 

프로젝트 소개 및 역할

 우리 팀은 오설록이라는 카페물품 관련 e-commerce 사이트를 모티브로 한 웹 사이트를 만들었다.

  • 인원 및 구성 : 프론트 3명 / 백엔드 2명
  • 프로젝트 기간 : 2022/08/29 ~ 2022/09/08
  • 프론트엔드 담당: 메인 페이지, 제품 페이지, 제품 상세 페이지, 로그인/회원가입,  장바구니, 결제 페이지 구현
  • 백엔드 담당 : 데이터 모델링 및 API 및 서버 구현

나는 프론트엔드 쪽으로 참여를 하였으며, 백엔드와의 통신은 포스트맨에서 제공하는 툴에 따라 API documentation을 만들어서 진행했다.

 

사용 기술

  • Front-end : React.js(개발환경은 CRU), sass, fetch, webpack(컴파일러는  babel)
  • Common : RESTful API
  • Community Tools : Slack, Zoom, Notion, Zepp
  • Version Control Tool : Git(flow는 github flow방식을 따름)

깃허브 링크

Front-end : https://github.com/cratuss/justcode-6-1st-osamloc-front

Back-end : https://github.com/wecode-bootcamp-korea/justcode-6-1st-osamloc-back

 

구현 기능

장바구니와 결제 페이지의 UI와 기능 구현을 맡았다. 

 

장바구니 페이지에서는 먼저 사용자의 가시성을 높이기 위해서 체크박스 상호작용에 중점을 두었다. 
전체선택부분 체크박스를 클릭하게되면 전체선택, 전체해제가 가능해지며, 전체해제를 하고 상품 하나하나의 체크박스를 다 체크해도 전체선택부분 체크박스가 자동으로 체크표시 되게 해놓았다. 
원리는 상위 컴포넌트에서 useState로 체크박스 전체에 대한 상태를 설정하고 하위 컴포넌트들이 모두 공유할 수 있도록 해놓았다.
그리고 선택 상품결제, 전체 상품결제 두가지를 만들어서 위에서 useState로 관리한 체크박스 전체 상태를 통해서 선택 상품결제는 체크박스 상태를 같이 결제 페이지로 보내주도록 구현했다.

결제 페이지에서는 외부api를 통해 정보를 받아오고 백엔드의 전달하는 것에 중점을 두었다.

외부 api는 두가지를 사용했고, 먼저 주소api는 상용자의 편의를 위해서 사용했고 다음에서 제공하는 주소찾기오픈api를 이용했다.
기본 주소를 모달창에 입력하면 우편번호 정보를 자동으로 가져오게 해주는 시스템이다.
두번째는 결제api다. 실제 배포에 중점을 두고 구현을 했기에 완성도를 높이기 위해서 실제처럼 모달창을 띄우고 테스트 결제가 되도록 했다. iamport에서 제공하는 오픈api를 사용했고, 결제수단은 사용자의 편의를 위해서 4가지로 구성했다.  

 

아쉬웠던 점,  배운 점

의외로 기능이나 백엔드와의 API통신이 아닌 UI를 구성하고 만드는데 있어서 어려움을 많이 느꼈다. 구체적으로 기능적인 것은 어느 정도 머릿속으로 로직이나 흐름이 그려지는데, UI는 디자인적인 부분도 좀 필요하고, 공부한다는 생각으로 css라이브러리를 사용하지 않고 처음부터 바닐라로 다 만들었기 때문에 어려웠고 아쉬움이 많이 남았다.  이에 따라 차후 지속적인 리펙토링을 통해 버전 업데이트를 꾸준히 할 생각이다.

 

또한 통신부분에서 백엔드분들과 생각하는 방식이 많이 달라서 소통을 많이 하였고, 앞으로는 주고받는 데이터나 변수 명을 초기 모델링 때 명확히 정하고 가야 될 것 같다고 생각했다.

 

이용자의 시선

물론 배포는 하지 않았지만 이번 프로젝트 모토를 배포에 중점을 고객입장에서 최대한 바라보려고 노력했다. 그 결과 회고록을 쓰는 날에 와서도 정말 많은 부분이 부족했다. 나의 페이지는 겉보기에만 번지르르해보이고 고객의 편의를 하나도 고려하지 않은 부분들이 너무 많았고 그 결과로 한 페이지를 마무리하는데만 2주에 프로젝트 기간 중 절반을 소모했다. 또 클론 코딩을 위해 오설록을 뜯어보면서 얼마나 사소한 부분부터 편의를 위해 많은 노력을 했는지도 알게 되었다.

 

최종적으로 시중에 서비스 되고 있는 웹,모바일 어플리케이션들이 왜 배포 이후에도 꾸준한 업데이트가 필요하게 되는지 이해하게 되었다.

 

협업

협업의 무게감을 많이 느꼈다. 일단 위에 아쉬웠던 점에서도 언급했지만, 초기 단계에 프론트 엔드 쪽과 백엔드 쪽이 너무 따로 노는 감이 있었다. 초기 vscode 파일 설정과 파일 명이나 코딩 방식에 대한 단순한 부분에 대해서만 함께하고 모두 각자 진행했고 그게 폐착으로 다가왔다. 특히 페이지를 어느 정도 마무리하고 백엔드와의 통신을 할때, 정말 많은 의견차이가 있었고, 이제 와서 그것을 해결할려고 하니 뜯어고쳐야 될 부분이 너무 많았다. 앞으로는 프론트 엔드 개발자 입장에서 db 모델링에는 꼭 참여해야겠다.

 

두번째로 단순히 강의를 듣고 코드를 짜고 할 때는, 문제가 생기면 바로바로 구글링해서 해결해나갈 수 있었지만, 협업에서는 소통하지 않으면 해결할 수 없는 문제가 많았다. 따라서 그 과정에 있어서 절대 안정해진 부분에 대해서 독단적으로 이럴 것 같다고 상상해서 코딩을 짜지말고 아주 사소한 부분부터 회의를 거쳐서 진행해야 될 것 같다고 느꼈다.

 

그리고 협업 flow에도 다양한 방식이 있다는 것과 협업 기간 중에 절반 이상은 회의와 리뷰의 연속이라는 것을 알게 되었다. 

 

마무리

개발자로서의 첫 발디딤이었다고 생각한다. 이전 교육은 어디까지 맛보기고, 협업이 실제 현장에서 경험할 수 있는 많은 부분을 겪게 해줬기 때문이다.

 

근데 사실 첫 프로젝트는 아니다. 이전에 저스트코드같은 교육기관에서 한 번, 일본 회사에서 연수를 받으면서 한 번, 총 두 번을 spring 기반의 풀스택으로 참여한 적이 있다. 그렇지만 프로젝트, 협업이라는게 할때마다 사람이 바뀌고 그에 따른 의견, 시선, 다양한 것들이 다양한 방식으로 충돌하다 보니 또 새로운 느낌이었다.

 

특히나 이전 프로젝트에서는 전부 spring을 기반으로 하고 프론트 엔드쪽은 따로 프레임워크를 사용하지 않았고, 이번에는 프론트엔드 쪽 하나만 깊게 파면서 React를 처음 활용해봤기에 더 새로웠던 것인지도 모르겠다.

 

동적 라우팅

  • 정적라우팅 즉, url에 다른 정보를 붙이지 않고 단순히 경로설정만을 위한 값이었다면, 동적라우팅은 url에 정보를 붙여서 url이 동적으로 변화하여 매번 props로 모든 정보를 받아오는 것이 아닌 동적으로 url의 정보를 바인딩해서 활용하거나 해당 url정보를 통해 api주소에서 데이터를 받아오는 용도이다.
  • 리엑트는 기본적으로 라우팅 시스템을 갖추고 있기 않기에 ‘react-router-dom’ 라이브러리 필요
  • 동적 라우팅에는 url에 파라미터를 붙여주는 두 가지 방식 path parameter, query parameter가 있다.

Path Parameter

  • url에 기존 정적 라우팅에서 하던 ‘/users’에서 ‘/users/:id’로 id라는 변수에 데이터를 담아서 보내준다.
  • id이외에 key값은 다양한 값이 가능하다.
  • useParams훅을 통해 받아온 url 정보에서 데이터를 뽑아낸다.
//1. 먼저 라투팅 파일에 url 설정  
<Router> 	
    <Routes> 		
        //기존 패스에 :key값을 추가 		
        <Route path='/product/:id' element={<ProductDetail />} /> 	
    </Routes> 
</Router>
//2. url을 보내줄 컴퓨터에서 정보 실어서 보내기  

//react-router-dom을 import 
import { useNavigate, useLocation, useParams } from "react-router-dom";  

function send() { 	 	
return ( 	  
        <> 			
            // 1번에서 설정한 url방식으로 뒤에 데이터 추가해서 보냄 			
            <Button onClick={() => { 				
                navigate(`/product/${productId}`); 			
            }}  			
            /> 	  
        </> 	
    );	 
}
//3. url을 받아온 컴포넌트 즉, 위에 경우 /product로 라우팅된 컴포넌트로 이동하여  

//url데이터를 set하여 직접 사용할 것인지 바인딩하여 그 정보로 api주소로 호출하여  
//다른 정보를 요청할 것인지  

//react-router-dom을 import 
import { useNavigate, useLocation, useParams } from "react-router-dom";  

function Users() {   
    //useParams로 유저 정보 가져오기   
    const params = useParams();    

    // params: { id: string }   
    console.log(params.id);    

    useEffect(() => {     
    // id를 기반으로 api 호출     
    // setState   
    } , [])    

    return <>{params.id}</> 
    // state 
}

 

Query Parameter

  • path parameter와 동일하게 url에 정보를 담아서 보내는 것이나 방식이 다르다.
  • ‘/products’에서 ‘/products?offset=10’로 offset이라는 변수에 데이터를 담아서 보내준다.
  • 여러개 보낼 경우 ‘&’구분자로 나눠준다. ex) /products?offset=10&limit=10
  • path parameter와 달리 따로 라우팅 파일에 url을 설정해주지 않는다.
  • useLocation훅을 통해 받아온 url 정보에서 데이터를 뽑아낸다.
  • 페이지에 리스트 같은 검색결과나 분류, 페이징 등에 따라 일부만 바뀌는 경우에 쓰인다.
//1. url을 보내줄 컴퓨터에서 정보 실어서 보내기  

//react-router-dom을 import 
import { useNavigate, useLocation, useParams } from "react-router-dom";  

function send() { 	 	
return ( 	  
        <> 			
            // 설정한 url방식으로 뒤에 데이터 추가해서 보냄 			
            <Button onClick={() => { 				
            navigate(`/product/${productId}`); 			
            }}  			
            /> 	  
        </> 	
    );	 
}
//2. url을 받는 컴포넌트에서 데이터를 받아 사용하거나 api주소에 데이터를 기준으로  

// 전체를 받아와서 url데이터를 set하여 직접 사용할 것인지 바인딩하여 그 정보로 api주소로 
// 호출하여 다른 정보를 요청할 것인지  

//react-router-dom을 import 
import { useNavigate, useLocation, useParams } from "react-router-dom";  

function Search() {    
    //useLocation훅을 바인딩   
    const location = useLocation();      

    //.search로 해당 url에서 ?부터 그 뒤를 string으로 가져옴   
    const query = location.search;      

    //가져온 url주소를 URLSearchParms에 담기   
    const urlSearchParams = new URLSearchParams(query);    

    //.get으로 url에서 설정한 key값으로 value받기  
    console.log(urlSearchParams.get('offset'));    

    //key가 여러개일때 전체를 가져올때	  
    console.log(urlSearchParams.entries());      

    return <>{query}</> 
}

 

중첩 라우팅

const App = () => {   
return (     
    <Routes>       
        <Route path="/user" element={<UserPage />} >      
        
        // /user/profile로 했을때 라우팅 되는 컴포넌트 Profile         
        <Route path="profile" element={Profile} />         
        
        // /user/account로 했을때 라우팅 되는 컴포넌트 Account         
        <Route path="account" element={Account} />       
        
        </Route>     
    </Routes>   
    ) 
}

 

'Monthly I Learned > 2022.08' 카테고리의 다른 글

8/24 - props.children  (0) 2022.08.24
8/23 - optional chaining  (0) 2022.08.24
8/22 - 인증 / 인가  (0) 2022.08.22
8/19 - useEffect와 prev  (0) 2022.08.22
8/18 - React props와 webpack  (0) 2022.08.22

 

props.children

  • 하나의 컴포넌트에 변하지 않는 값들, 재사용 가능한 부분들을 하나의 컴포넌트에 넣고 그 안에 props.children이라고 선언하여 활용하는 것
  • children이란 import된 상위 태그로 감싸진 부분을 말한다.
  • 보통 Layout설정의 많이 쓰이는 것으로 보인다.

 

1. 상위 컴포넌트 구현

//직접 페이지에 노출된다기보다는 아래 컴포넌트에 살을 붙여주는 역할이다.  

//Component훅 선언 
import React, { Component } from "react";  

//여기서 인자로 들어온 props는 자식한테 props를 보내주겠다는 의미가 아닌  
//FormLayout컴포넌트를 임포트해서 FormLayout태그로 감싸놓은 부분 
//즉, props.children을 가지고 와서 집어넣겠다는 의미이다. 

function FormLayout(props) {   
    return (     
        <div className="Form">       
        <header>         
            <div className="logo" />       
        </header>        

        //children이 들어갈 위치       
        // 컴포넌트로 감싸진 부분이 이곳에 들어옴
        {props.children} 

    ); 
} 

export default FormLayout

 

2. props.children 구현 

//실제로 불러와지는 컴포넌트로서 위에 상위 컴포넌트로 살을 붙여 화면에 렌더링된다.  

// props.children이 적힌 위에 상위 컴포넌트를 임포트 
import FormLayout from "./FormLayout"  

//이 것은 상위로부터 받아온 props로 FormLayout과는 관계없음 
function Form(props) {   
    const { type, title, inputData } = props;    
    return (     
        //임포트한 상위 컴포넌트 태그로 감싸고 작성     
        
        //감싼 이 부분이 FormLayout의 props.children이 된다.     
        <FormLayout>       
            <h2>{title}</h2> 			 
            <div>         
            {inputData.map((input, idx) => (           
                <Input             
                key={idx}             
                type={input.type}             
                text={input.text}           
                />        
            ))}       
            </div>         
        </FormLayout>   
        //
    ); 
}

 

'Monthly I Learned > 2022.08' 카테고리의 다른 글

8/25 - 동적 라우팅  (0) 2022.08.25
8/23 - optional chaining  (0) 2022.08.24
8/22 - 인증 / 인가  (0) 2022.08.22
8/19 - useEffect와 prev  (0) 2022.08.22
8/18 - React props와 webpack  (0) 2022.08.22

optional chaining

  • (객체or배열or함수)?.xxx
  • xxx에는 조건이 객체인지 배열인지 함수인지에 따라 아래값이 들어온다.
    // 결국 에러가 나 존재하지 않을 수도 있는 모든 값에 붙여준다. 
    alert( obj?.prop ) 
    alert( obj?.[expr] ) 
    alert( arr?.[index] ) 
    alert( func?.(args) ) 
    //?앞에 값이 없다는 가정에 모두 undefined를 출력  
    
    obj.obj?.prop 
    // 맨앞 obj가 존재하지 않는다면 에러가 나온다.  
    // 위에 경우는 obj?.obj?.prop로 바꿀 수 있다.
  • 해당 객체나 배열, 함수가 존재하지 않는다면 즉, null이나 undefined라면 원래는 에러가 나오나 그렇지 않고 위에 한 줄 전체가 undefined를 반환하게 해주는 것이다. 만약 존재한다면 뒤에 xxx를 실행한다.

 

'Monthly I Learned > 2022.08' 카테고리의 다른 글

8/25 - 동적 라우팅  (0) 2022.08.25
8/24 - props.children  (0) 2022.08.24
8/22 - 인증 / 인가  (0) 2022.08.22
8/19 - useEffect와 prev  (0) 2022.08.22
8/18 - React props와 webpack  (0) 2022.08.22

'Web > Javascript' 카테고리의 다른 글

Async와 Await  (0) 2022.08.23

'Web > web통신' 카테고리의 다른 글

Http 통신 과정  (0) 2022.08.24

+ Recent posts