SurveyProject_Front) 클론코딩 정리하기

iamokian·2022년 7월 29일
0

리액트의 실무기회를 접할 수 없는 나는 인강이나 서적을 따라하는 위주로 공부를 했다.(직접 무언가를 만들어보면 좋으련만 혼공에 막힘이 꽤있다..하지만 이런식의 래퍼런스를 쌓고 꼭 도전해봐야 하는것 중 하나!!!) 이것 저것 따라하며 막히기도 하면서 기본적인것에 대한 이해도를 쌓고있는데 실무처럼 진행해볼 수 있는 인강을 발견해 바로 결제를 해보았다.
스터디파이강의에서 발견.

이 강의는 실무처럼 진행해볼수 있도록 모의 개발같은 느낌이 강하니 기본적인 react의 이해도를 쌓고 보는것이 수월할 것 같았다. 다행히 그전에 이것저것 봐두고 따라해본게 있으니 기초단계는 따라가볼만 했다. (물론 페이지 나뉘고 데이터가 붙으면서 멘탈이 나갔지만) 일단 그래서 어려웠고, 아직 모르는 부분도 있지만 단순히 따라하고 끝이 아니게끔, 프로젝트의 맥락이 이렇게 흘러갔다는것을 이해해볼겸 후기작성을 해본다.

이 강의를 선택한 이유중에 하나인 이것.

쁘띠하지만 기획서가 있었다. 정말 실전같은 느낌을 보여준다. 무튼

  1. 기획서를 보고 요구하는 사항이 뭔지, 그에 필요한 기술이 뭔지, 더불어 데이터를 어떻게 정의해야하는지 생각해볼 수 있었다. 주고 받아야 할 데이터가 무엇인지 어떤구조로 짜야하는지 물론 이렇게 생각하거나 실전에 옮겨도 api에 정의해둔 데이터의 구조와 임의로 짜둔 데이터가 무조건 같을 수 없다는것을 알아둬야 한다. 여러 인강을 봤을 때 들은말은 사전조율이나, 효율적인 구조를 사용하길 권장해주긴 하였다. 그렇게 대략적으로 만들어진 필요 데이터는
  • 설문 고유 아이디값
  • 설문별 타이틀, 설명, 타입(셀렉트,인풋), 필수입력여부등등 이었다.

세부적인 환경세팅중에 꼭 필요하다 싶은것은

  • prettierrc를 설정해 저장할때마다 코드를 깔끔히 정리해주었고
  • eslint-plugin-simple-import-sort플러그인 설치로 import 구문들을 알아서 정렬받았(?)다.(정렬기준을 따로 세팅해주어야함)

이것들.

그리고 본격적으로 컴포넌트를 쪼개보는 시간이 왔다.

  1. 데이터던 뭐던 우선 컴포넌트가 만들어져야 한다. 그래야 데이터들이 제자리를 찾아갈테니:) 이미지와 같이 한 화면에서도 굉장히 컴포넌트가 세분화되는걸 알 수 있었다. 이러면 경우에 따라 재사용하기도 좋고, 쓸데없이 페이지를 나눠 데이터를 분산시킬일도 없다는것을 알 수 있었다.

그리고 그 과정에서 컴포넌트들을 따로 폴더화시켰다. 이것이 실제 서비스되는 제품이라면 유지보수에도 용이할 것 같다.

📦components
 ┣ 📂ActionButtons
 ┃ ┗ 📜index.js
 ┣ 📂Bar
 ┃ ┗ 📜index.js
 ┣ 📂Body
 ┃ ┗ 📜index.js
 ┣ 📂Button
 ┃ ┗ 📜index.js
 ┣ 📂Desc
 ┃ ┗ 📜index.js
 ┣ 📂ProgressIndicator
 ┃ ┗ 📜index.js
 ┣ 📂QuestionBox
 ┃ ┗ 📜index.js
 ┣ 📂SelectInput
 ┃ ┗ 📜index.js
 ┣ 📂TextAreaInput
 ┃ ┗ 📜index.js
 ┣ 📂TextInput
 ┃ ┗ 📜index.js
 ┗ 📂Title
 ┃ ┗ 📜index.js
  1. 그리고 알게된 Routing, SPA에서의 라우팅은 브라우저의 히스토리를 조작하는 방식으로 동작하는 것을 알 수 있었다. 브라우저는 뒤로,앞으로같은 이동을 동작하기 위해 브라우저의 히스토리객체를 사용한다. 여기에서는 react-router-dom을 사용해 해당 주소로 실제 http요청을 보낸것은 아니지만 페이지를 이동한것과 같은 효과를 보여준다.

해당 프로젝트에서는 설문폼과 설문완료 딱 이렇게 2개의 페이지로 분류를 했다.

📦pages
 ┣ 📂CompletionPage
 ┃ ┗ 📜index.js
 ┗ 📂SurveyPage
 ┃ ┗ 📜index.js

질문들의 인덱스값과 링크의 쿼리스트링으로 불러온값을 매치에 페이지별 스텝을 나눠주었다.

  1. 그리고 컴포넌트들을 styled-components로 스타일링 해줬다. 아래와 같은 피그마시안을 통해 각각 css속성값을 확인해 스타일링 해주고, 버튼같은 경우 타입에 따라 다양한 형태로 보여지는것에 대한 경우의 수를 대비해 스타일변수를 선언해두고 그것을 사용해 함수형식으로 css를 작성해 코드를 간결하게 짤수 있었다.

변수폴더를 따로 만들어두어 스타일변수가 아닌 것들에 대한것도 이곳에서 관리 할 수 있다.

📦constants
 ┗ 📜color.js
export const PRIMARY = {
 BUTTON: {
   DEFAULT: {
     COLOR: '#fff',
     BACKGROUND: '#6542F1',
   },
   HOVER: {
     COLOR: '#fff',
     BACKGROUND: '#9982F4',
   },
   PRESSED: {
     COLOR: '#fff',
     BACKGROUND: '#4B31B0',
   },
   DISABLED: {
     COLOR: '#D0CDCD',
     BACKGROUND: '#EDEDED',
   },
 },
};

export const SECONDARY = {
 BUTTON: {
   DEFAULT: {
     COLOR: '#3A3A3A',
     BACKGROUND: '#DEDEDE',
   },
   HOVER: {
     COLOR: '#3A3A3A',
     BACKGROUND: '#F3F3F3',
   },
   PRESSED: {
     COLOR: '#3A3A3A',
     BACKGROUND: '#B8B7B9',
   },
   DISABLED: {
     COLOR: '#D0CDCD',
     BACKGROUND: '#EDEDED',
   },
 },
};

export const TERTIARY = {
 BUTTON: {
   DEFAULT: {
     COLOR: '#6542F1',
     BACKGROUND: '#fff',
     BORDER: '#6542F1',
   },
   HOVER: {
     COLOR: '#9982F4',
     BACKGROUND: '#fff',
     BORDER: '#9982F4',
   },
   PRESSED: {
     COLOR: '#4B31B0',
     BACKGROUND: '#F0EDFC',
     BORDER: '#4B31B0',
   },
   DISABLED: {
     COLOR: '#D0CDCD',
     BACKGROUND: '#F6F6F6',
     BORDER: '#EDEDED',
   },
 },
};
import styled from 'styled-components';

import { PRIMARY, SECONDARY, TERTIARY } from '../../constants/color';

const colorMap = {
  PRIMARY,
  SECONDARY,
  TERTIARY,
};

const Button = styled.button`
  min-width: 200px;
  padding: 16px 24px;
  border-radius: 4px;
  color: ${({ type }) => colorMap[type].BUTTON.DEFAULT.COLOR};
  background: ${({ type }) => colorMap[type].BUTTON.DEFAULT.BACKGROUND};
  font-weight: bold;
  font-size: 18px;

  border: ${({ type }) =>
    type === 'TERTIARY'
      ? `1px solid ${TERTIARY.BUTTON.DEFAULT.BORDER}`
      : 'none'};

  &:hover {
    color: ${({ type }) => colorMap[type].BUTTON.HOVER.COLOR};
    background: ${({ type }) => colorMap[type].BUTTON.HOVER.BACKGROUND};
    border: ${({ type }) =>
      type === 'TERTIARY'
        ? `1px solid ${TERTIARY.BUTTON.HOVER.BORDER}`
        : 'none'};
  }

  &:active {
    color: ${({ type }) => colorMap[type].BUTTON.PRESSED.COLOR};
    background: ${({ type }) => colorMap[type].BUTTON.PRESSED.BACKGROUND};
    border: ${({ type }) =>
      type === 'TERTIARY'
        ? `1px solid ${TERTIARY.BUTTON.PRESSED.BORDER}`
        : 'none'};
  }

  &:disabled {
    color: ${({ type }) => colorMap[type].BUTTON.DISABLED.COLOR};
    background: ${({ type }) => colorMap[type].BUTTON.DISABLED.BACKGROUND};
    border: ${({ type }) =>
      type === 'TERTIARY'
        ? `1px solid ${TERTIARY.BUTTON.DISABLED.BORDER}`
        : 'none'};
  }
`;

export default Button;

여기에서 Css-in-JS의 매력을 느낄 수 있었고, styled-components에서도 props를 받아와 사용할 수 있다는것을 알 수 있었다.

  1. 그리고 핵심이라 생각되는 전역 상태 관리. 앞선 메모프로젝트를 클론 하며 느꼈던 불편함에는 props를 부모이상의 컴포넌트에서부터 쭉 내려받아 사용하는 것이었다. 그리고 그렇게 되면 리렌더링이 필요 없는 중간 컴포넌트까지 리렌더링이 발생 할수 있다는것을 알 수 있었다. 이것은 서비스의 성능을 저하시키는 요인이 된다. 하여 전역 상태를 만들어 사용한다.

컴포넌트 내부에 있어야 할 데이터를 바깥으로 빼내어 관리하는 것이다. 그리고 그렇게 빼낸것에서 필요한 컴포넌트에만 데이터를 전달해준다. 그리하여 사용된 라이브러리가 Recoil이었다.

📦stores
 ┣ 📂answers
 ┃ ┗ 📜atom.js
 ┗ 📂survey
 ┃ ┣ 📜questionLengthState.js
 ┃ ┗ 📜surveyState.js

일반적으로 전역상태는 stores에 보관한다. 상태라 하면 useState와 비교해 생각할 수 있지만, useState는 하나의 컴포넌트 안에서만 사용이 되고, recoil에서 사용되는 저장소 atom은 여러개의 컴포넌트에 연결이 될 수 있다.(atom은 여러개를 만들 수 있음) 그리고 저장소에서 값을 가져올 때는 selector를 사용해 상태값을 원하는 형태로 필터해 가져오는 형식으로 사용했다. 이런식으로 하게되면 만약 저장소의 값이 바뀌더라도 저장소를 구독중인 컴포넌트에서 필요로 하는 데이터가 변동된게 아니라면 불필요한 리렌더링이 일어나지 않는다.

  1. 데이터를 관리하고 주고받으며 custom Hook을 접했다. 하나 이상의 Hooks를 조합해서 만든 새로운 Hooks라고한다:) 주로 반복되서 사용되는 훅스를 조합해 만들어낸다. 해당 클론코딩에서는 atom에서 데이터를 추출하고 가공하는 과정을 hooks로 만들었다.
📦hooks
 ┣ 📜useAnswers.js
 ┣ 📜useCurrentAnswer.js
 ┣ 📜useCurrentQuestion.js
 ┣ 📜useRequiredOption.js
 ┣ 📜useStep.js
 ┗ 📜useSurveyId.js

Hooks라는 폴더를 따로 만들어서 각 hooks마다 따로 파일링 해주었다.

// 만든 hooks
import { useParams } from 'react-router-dom';

function useStep() {
  const params = useParams();

  return parseInt(params.step);
}

export default useStep;
// hooks 불러와 사용하기
import { useRecoilValue } from 'recoil';

import surveyState from '../stores/survey/surveyState';
import useStep from './useStep'; // hooks 불러오기

function useCurrentQuestion() {
  const step = useStep(); // 반복적으로 사용되는 Hooks
  const surveyData = useRecoilValue(surveyState);
  const questions = surveyData?.questions || [];

  return questions[step];
}

export default useCurrentQuestion;

진행을 하며 많이 사용된 useStep()를 custom hooks로 여러번 재사용 하였다.

6.이제 대망의(?) API연동하기. 이전 과정까지는 프로젝트 내부에 설문데이터를 임시로 만들어 사용했지만 실전에서는 그렇지 않다. 외부 서버에 짜여진 데이터들을 불러와 프론트단과 연결시켜줘야 한다. 그러기 위해 API를 통해 데이터를 받아온다. 그것을 연동하고자 Axios를 사용했다. 기본적인 사용법을 볼수있음

📦services
 ┣ 📂apis
 ┃ ┗ 📜mainApi.js
 ┣ 📜getSurvey.js
 ┗ 📜postAnswers.js

그렇게 하여 기본 api주소를 만들어 두고,

import axios from 'axios';

const mainApi = axios.create({
  baseURL: 'http://localhost:3001',
});

export default mainApi;

요청하고, 불러올때 사용하기.

import mainApi from './apis/mainApi';

function getSurvey(surveyId) {
  return mainApi.get(`/surveys/${surveyId}`);
}

export default getSurvey;
import mainApi from './apis/mainApi';

function postAnswers(surveyId, data) {
  return mainApi.post('/answers', { surveyId, data });
}

export default postAnswers;

전체적인 큰 흐름은 이런식으로 흘러갔고, 나머지는 기능구현의 문제였다. 그 부분들이 멘탈을 많이 나가게하긴 했다.

그리고 아직 Admin이 남았다..:)


admin페이지 클론코딩일지 정리 완! 최종느낌까지 쓱삭 🖍

profile
필기하고 기록하고

0개의 댓글