사상 두번째 프로젝트 - #1 기본 틀 제작

sham·2021년 10월 20일
0

Betti 개발일지

목록 보기
1/14

git clone -b dev --single-branch https://github.com/rkskekzzz/betti.git Betti

위 명령어를 통해 변경 사항이 적용된 dev 브랜치의 내용을 클론해온다.

UI는 피그마에 잘 정리되어 있기에 내가 할 일은 저 디자인 그대로 구현하는 것.

라우팅을 해줄 페이지는 로그인 페이지, 메인 페이지.

메인 페이지에서도 세부적인 url나 작동에 따라 변경을 해주어야 하지만, 왼쪽의 팀 처럼 그대로 유지해야 할 것도 있다.

구현 과정

구조

메인 페이지는 크게 두 개의 컴포넌트로 구분할 수 있다.

  • TeamBar - 내가 속한 팀을 나타냄, 팀을 클릭할 때마다 MainScreen가 변해야 한다. curPage에 따라 MainScreen 가 변하기 때문에 curPage를 수정하는 콜백함수를 props로 보낸다.
  • MainScreen - 팀에 해당하는 테스트 목록을 표시, teamData 배열의 curPage를 index로 인식해서 뿌린다.

주요 State는 다음과 같다.

  • teamData - 팀에 대한 정보를 각 요소가 객체인 배열로 관리한다. 컴포넌트를 렌더링할 때마다 서버와 통신해서 갱신을 해주어야 하니 임시적으로 useEffect를 써서 렌더링 될때마다 고정된 값으로 변화시킨다. TeamBar 에서 팀을 추가하면 갱신된다.
  • curPage - 현재 사용자가 선택 중인 팀. 기본값으로 첫번째(0) 배열을 보여준다. TeamBar 에서 팀을 클릭할 때마다 갱신된다.

TeamBar.jsx

import React from 'react';
import './TeamBar.scss';
const TeamHeader = () => {
  const teamOptionHandler = () => {
    alert('option event!');
  };
  return (
    <div className="team-header">
      Betti!
      <img
        className="option-icon"
        src="assets/option.png"
        onClick={teamOptionHandler}
      />
    </div>
  );
};

const TeamBody = ({ teamData, changePageEvent }) => {
  const addTeamHandler = () => {
    alert('팀 만드는 모달창');
  };

  return (
    <div className="team-body">
      {teamData.map(e => {
        return (
          <div
            className="team"
            key={e.index}
            onClick={() => changePageEvent(e.index)}
          >
            {e.name}
          </div>
        );
      })}
      <div className="separation">{}</div>
      <div className="team team-add" onClick={addTeamHandler}>
        +
      </div>
    </div>
  );
};

const TeamBar = ({ teamData, changePageHandler }) => {
  return (
    <div className="team-bar">
      <TeamHeader />
      <TeamBody teamData={teamData} changePageEvent={changePageHandler} />
    </div>
  );
};

export default TeamBar;

TeamHeader, TeamBody 로 구분된다.

메인 페이지에서 props로 받은 teamData를 map을 통해 div 엘리먼트로 리턴시켜준다.

팀을 클릭할 때마다 MainScreen은 변해야 한다. changePageEvent는 curPage를 인자에 담긴 값으로 변경한다. 어떤 것을 클릭했는지 분간해야 하기 때문에 같이 넣어준 key값을 보내도록 처리한다.

MainScreen.jsx

import { React, useEffect } from 'react';
import './MainScreen.scss';

const MainScreenHeader = ({ teamData, curPage }) => {
  return (
    <div className="main-screen-header">
      <div className="main-screen-team">{teamData[curPage].name}</div>
      <div className="main-screen-option">
        <img src="assets/teamIcon.png" />
        <div>+ {teamData[curPage].test.length}</div>
        <img src="assets/etc.png" />
      </div>
    </div>
  );
};

const MainScreenBody = ({ teamData, curPage, curTest, changeTestEvent }) => {
  const addTestEvent = () => {};

  return (
    <div className="main-screen-body">
      <div className="main-screen-tests">
        {teamData[curPage].test.map((e, i) => {
          return (
            <div
              key={`${curPage}-${i}`}
              className="main-screen-test"
              onClick={() => changeTestEvent(i)}
            >
              {e}
            </div>
          );
        })}
        <div onClick={addTestEvent} className="main-screen-test add">
          +
        </div>
      </div>
      <div className="main-screen-test-info">
        {teamData[curPage].test[curTest]}
      </div>
    </div>
  );
};

const MainScreen = ({ teamData, curPage, curTest, changeTestEvent }) => {
  useEffect(() => {
    console.log(teamData[curPage]);
  }, [curPage]);
  return (
    <div className="main-screen">
      <MainScreenHeader teamData={teamData} curPage={curPage} />
      <MainScreenBody
        teamData={teamData}
        curPage={curPage}
        curTest={curTest}
        changeTestEvent={changeTestEvent}
      />
      <div className="separation"></div>
    </div>
  );
};

export default MainScreen;

MainScreenHeader, MainScreenBody로 구분된다.

현재 선택한 팀에 대한 정보를 뿌려야 한다. 부모 컴포넌트에서 state로 관리하는 curPage를 추가로 props에 담아 보낸다. teamData[curPage]로 지금 선택 중인 팀에 대한 정보, 정확히는 팀의 테스트에 대한 정보를 map으로 뿌린다.

각 테스트를 클릭할 때마다 표시하는 테스트도 변해야 한다.

중요

글자를 정중앙에 놓고 싶다면

  • 가로 정렬 - text-align: center;
  • 세로 정렬 - padding: 10px 0;
    • padding, margin, border를 조절할 때 [상, 우, 하, 좌] 나 [상하, 좌우]로 조절하게 되는데, 0을 넣게 되면 자동으로 해당 값이 초기화되어 정렬이 된다.

html 태그는 브라우저에서 설정한 고유의 값들을 가지고 있기 때문에 * { padding : 0; margin : 0;} 로 초기화한 후 값을 다시 설정하기도 한다.

컴포넌트 쪼개기

컴포넌트에 대해서는 다음 링크로.

HTML의 element가 있다면 React에는 Component가 존재한다.

props(input)를 다르게 주면 동일한 UI 형태에 각각 다른 Component(output)를 만들어낸다.

이 컴포넌트를 기본 단위로 해서 기능별로 쪼갠 후 합성할 수 있다는 것이 React의 큰 강점이라고 할 수 있다.

import { React, useEffect } from 'react';
import './MainScreen.scss';

const MainScreen = ({ teamData, curPage }) => {
  useEffect(() => {
    console.log('Teamdata : ');
    console.log(teamData[curPage]);
  }, [curPage]);
  return (
    <div className="main-screen">
      <div className="main-screen-header">
        <div className="main-screen-team">{teamData[curPage].name}</div>
        <div className="main-screen-option">
          <img src="assets/teamIcon.png" />
          <div>+ {teamData[curPage].test.length}</div>
          <img src="assets/etc.png" />
        </div>
      </div>
      <div className="separation"></div>
      <div className="main-screen-body">
        <div className="main-screen-tests">
          {teamData[curPage].test.map((e, i) => {
            return (
              <div key={`${curPage}-${i}`} className="main-screen-test">
                {e}
              </div>
            );
          })}
          <div className="main-screen-test add">+</div>
        </div>
        <div className="main-screen-test-info">시각 정보 제공</div>
      </div>
    </div>
  );
};

export default MainScreen;

위 코드는 MainScreen이라는 메인 페이지의 본문을 담당하는 컴포넌트에 대한 코드다.

하나의 컴포넌트에 모든 코드를 집어넣어도 구현은 문제없이 되지만, 아무래도 가독성이 떨어진다.

UI만 구현해서 그렇지, 함수들을 작성하면 가독성은 더더욱 떨어질 것이다.

import { React, useEffect } from 'react';
import './MainScreen.scss';

const MainScreenHeader = ({ teamData, curPage }) => {
  return (
    <div className="main-screen-header">
      <div className="main-screen-team">{teamData[curPage].name}</div>
      <div className="main-screen-option">
        <img src="assets/teamIcon.png" />
        <div>+ {teamData[curPage].test.length}</div>
        <img src="assets/etc.png" />
      </div>
    </div>
  );
};

const MainScreenBody = ({ teamData, curPage }) => {
  return (
    <div className="main-screen-body">
      <div className="main-screen-tests">
        {teamData[curPage].test.map((e, i) => {
          return (
            <div key={`${curPage}-${i}`} className="main-screen-test">
              {e}
            </div>
          );
        })}
        <div className="main-screen-test add">+</div>
      </div>
      <div className="main-screen-test-info">시각 정보 제공</div>
    </div>
  );
};

const MainScreen = ({ teamData, curPage }) => {
  useEffect(() => {
    console.log('Teamdata : ');
    console.log(teamData[curPage]);
  }, [curPage]);
  return (
    <div className="main-screen">
      <MainScreenHeader teamData={teamData} curPage={curPage} />
      <MainScreenBody teamData={teamData} curPage={curPage} />
      <div className="separation"></div>
    </div>
  );
};

export default MainScreen;

보통은 각 컴포넌트를 모듈화(파일로 분할)해서 자식 컴포넌트를 import하는 편이지만, 편의를 위해 한 파일에 전부 넣었다.

위의 코드를 기능별, 구조별로 컴포넌트화해서 합성한 코드다. 방금 전의 코드보다 가독성이 훨씬 좋아진 것을 볼 수 있다. 어떤 컴포넌트가 어떤 역할을 하는지도 보다 직관적으로 변했다.

다음과 같은 작업에는 (개인적으로 생각하는) 장점이 하나 더 있다. 크롬 확장 프로그램 중 React Developer Tools를 사용하면 컴포넌트를 기준으로 조회가 가능하다.

Screen Shot 2021-10-18 at 5.31.52 PM.png

Screen Shot 2021-10-18 at 5.32.20 PM.png

컴포넌트 별로 분리해놓으면 조회하는 것이 조금 더 직관적이게 된다.

써놓고 보니 딱히 큰 장점은 아닌 것 같다.

콜백함수에 인자 넣기

리액트에서 콜백함수를 쓰게 될 일은 많다. 콜백함수를 사용할 때 ()를 붙이게 되면 즉시 실행을 의미하기에 함수 이름만 사용하는데, onClick 이벤트처럼 인자를 담아서 보내야 할 일이 생긴다. 분명 생긴다.

이 때 콜백함수에 인자를 넣어야 하는데, ES6 문법의 화살표 함수를 이용하면 간단하게 해결된다.

const Button = () => {
  const [value, setValue] = useState(value);
  return (
    <>
      <div onClick={() => CallbackFunction(value)}></div> // 즉시 실행!
      <div onClick={() => CallbackFunction(value)}></div> // 인자 전달!
    </>
  );
};

이슈

높이를 꽉 차게 하기 위해서는

https://velog.io/@ursr0706/height100에-대하여

import 관련 이슈

스크린샷 2021-10-17 오후 2.23.31.png

경로 설정 시 자꾸만 인식을 못하는 오류 발생. 같은 디렉토리에 넣어 한 번 연결되고 나면 모듈을 이동해도 자동으로 갱신되는 점을 이용해 해결.

jsconfig.js 파일을 이용해 절대경로를 변경해줄 수 있다.

map으로 컴포넌트를 뿌릴 때 오류

https://devbirdfeet.tistory.com/47

스크린샷 2021-10-17 오후 2.56.49.png

스크린샷 2021-10-17 오후 2.56.38.png

team에 관한 데이터를 컴포넌트가 만들어질 때마다 갱신해주고 TeamBar 컴포넌트에 props으로 보내주어서 map으로 뿌리려고 했는데 에러가 발생했다.

문제의 원인은 props으로 보내준 teamData가 비어있을 때 map을 시도했기 때문이었다. 데이터가 들어오지 않았음에도 렌더링은 진행되기 때문...T^T

&& 연산자를 앞에 붙여줌으로써 데이터가 참일 때만 렌더링하게 수정했더니 해결되었다.

수정

스크린샷 2021-10-17 오후 11.47.36.png

애초에 Main에서 teamData가 존재할 때 props를 보내게끔 && 연산자를 붙여줬다.

팀에 따라 다른 화면을 보여줄 때

  • 라우터, 쿼리스트링으로 구분해서 보여줄까? - 각 쿼리스트링에 해당하는 컴포넌트를 전부 만들거나 props로 쿼리에 따라 다른 값을 주어야 한다.
  • 같은 라우터에서 현재 값에 따라 다른 화면을 보여줄까? - 모든 페이지에 대한 정보와 현재 페이지 값을 보내주어야 한다.

데이터를 기다려야 할 때

스크린샷 2021-10-17 오후 11.16.03.png

스크린샷 2021-10-17 오후 11.16.09.png

메인 페이지에서 State로 관리하고 있는 teamData는 렌더링을 시작할 때 서버로부터 데이터를 받아와야 할 것이다. 서버 연결을 하지 못했기에 useEffect로 비슷한 구현을 했으나 데이터를 채 받아오기도 전에 props로 텅텅 빈 teamData를 뿌리는 이슈가 발생했다.

Screen Shot 2021-10-18 at 5.14.03 PM.png

앞선 map 관련 이슈와 동일한 이유였기에 main에서도 teamData의 값이 존재할 때 컴포넌트를 뿌리도록 해주었다.

GH001: Large files detected. You may want to try Git Large File Storage

Screen Shot 2021-10-18 at 8.22.40 PM.png

에러 메세지로 구글링해보니 100MB 이상의 파일을 push하려고 해서 발생한 오류라고 한다. 어쩌다 들어간 파일인지는 모르겠지만 해당 파일을 삭제한 커밋을 추가해서 push 해도 동일한 오류가 발생했다.

Screen Shot 2021-10-18 at 8.22.48 PM.png

위에서 3번째 커밋에 그 커다란 파일이 들어갔었다. git reset HEAD ^, git reset HEAD ^~{삭제하고 싶은 커밋 수} 로 로컬의 커밋을 삭제할 수 있다고 한다. 처음 큰 파일이 포함된 커밋까지 삭제해주고 다시 커밋하니 정상 작동했다.

맥 초기화

슬랙과 다음 링크의 도움을 받았다.

클러스터 맥에서 brew를 설치해서 npm도 설치한 후 리액트 작업을 하고 있었는데, 갑자기 한 순간에 brew, npm, node가 사라져버렸다... 아무런 전조도 없이 발생한 이슈라서 지금도 원인을 모르겠다. 다시 설치하는 과정을 남기지만 이 글을 다시 보는 일이 없기를 바랄 뿐이다.

  • brew 설치

둘 중에 되는 방법을 사용.

curl -fsSL https://rawgit.com/kube/42homebrew/master/install.sh | zsh

rm -rf HOME/.brew && git clone --depth=1 [https://github.com/Homebrew/brew](https://github.com/Homebrew/brew) $HOME/.brew && echo 'export PATH=HOME/.brew/bin:$PATH' >> $HOME/.zshrc && source $HOME/.zshrc && brew update

npm(node 포함) 설치

brew install node

컨벤션

함수 컨벤션

onClick : 기능 + Handler?

콜백(props)로 받을 때는 기능 + Event?

컴포넌트 컨벤션

기능

기능Header

기능Body

className 컨벤션

kebab-case

한 컴포넌트를 감싸는 엘리먼트는 컴포넌트와 같은 이름으로

동일한 컴포넌트를 여러개 감싸는 엘리먼트는 복수형.(+s)

main-page에서 파생되었다면 자식 엘리먼트는 main-page- 로 통일.

profile
씨앗 개발자

0개의 댓글