1차 프로젝트 회고록

류창선·2023년 9월 17일
1

front-end

목록 보기
36/39

이 글을 지난 9월 11일부터 15일까지 진행된 미니 프로젝트의 과정을 아카이빙하기 위해 작성되었습니다.

1. 프로젝트 인트로

1.1. 목표

  • SNS 쓰레드 서비스와 유사한 UI의 Wereads라는 가상의 서비스를 개발하는 것이 1차 프로젝트의 목표였습니다.
  • 필수 구현 사항은 회원 가입과 로그인으로 이어지는 일련의 과정, 그리고 쓰레드 목록 표시와 쓰레드 글쓰기를 통한 서버와의 통신이었습니다.

1.2. 분석

  • 기 전달된 Wereads의 Figma 디자인은 플로우가 한눈에 들어오지 않았기에 프로젝트가 시작되기 전, 주말에 시간을 내어 내용을 정리하였습니다.









2. 기술 스택

2.1. Front-End Tech Stack

2.1.1. React

2.1.1.1. Component

  • React는 컴포넌트 단위로 개발하는 JavaScript 라이브러리인 만큼, 페이지 컴포넌트에 여러 컴포넌트를 import하여 구성하는 방식을 선택했습니다.

  • 위 스크린샷에서도 확인할 수 있듯이 Button을 기능별로 관심사 분리했습니다. 간단하게 BackButton을 살펴보겠습니다.

// BackButton.js
<button
  className="back-btn"
  type={type}
  aria-label="뒤로 가기"
  onClick={goToBack}
  >
	<BackIcon />
    뒤로
</button>
  • pros는 className, type, onClickaria-label을 더해서 웹 접근성 처리를 하도록 했습니다. 따라서 스크린 리더기(NVDA, VoiceOver 등)에서는 BackButton에 focus한 순간, 뒤로 가기 버튼이라 읽어줄 것입니다.
  • BackButton의 기능은 단순합니다. 유저가 진입했던 바로 직전의 경로로 되돌려 보낸다는 것이 전부입니다. 그래서 JavaScript의 history.go(-1)과 같은 기능을 수행하는 navigate(-1)을 함수로 만들어 onClick 이벤트 핸들러에 연결했습니다. 이로써 BackButton은 어떤 페이지 컴포넌트에서 import 하더라도 자신의 기능을 문제 없이 수행할 수 있게 되었습니다.
  • Button 컴포넌트는 기능적으로 열려 있어야 합니다. 무슨 의미인가 하면, 페이지 컴포넌트에서 내려주는 여러 기능을 아무런 거부 없이 받아들여야 한다는 뜻입니다.
// Button.js
function Button(props) {
  const {
    type = 'button',
    shape = 'solid',
    scale,
    text,
    action,
    disabled,
    onClick,
  } = props;

  // props
  // - type: [String] button(default) / submit / reset
  // - shape: [String] solid(default) / outline / text
  // - scale: [String] large(default) / small
  // - text: [String]
  // - action: [String] delete
  // - disabled: 조건이 상이하므로 페이지마다 다른 삼항 조건문 적용

  return (
    <button
      className="btn"
      type={type}
      shape={shape}
      scale={scale}
      aria-label={text}
      action={action}
      onClick={onClick}
      disabled={disabled}
    >
      {text}
    </button>
  );
}
  • Button 컴포넌트는 props가 많습니다. 여러 UI를 소화할 수 있어야 하기 때문입니다. type의 기본값은 button으로 정했습니다. submit인 로그인과 회원가입 페이지에서의 버튼만 value를 바꿔주면 됩니다.
  • shape props는 outline이 없는 경우와 있는 경우, 그리고 단순한 텍스트형으로 구분한 결과입니다.
  • scale은 크기별, text는 버튼 명칭과 웹 접근성 처리에 사용되었고, action의 경우는 수정/삭제라는 기능별로 분리하기 위해 선언한 props입니다.
  • disabled는 button 태그의 attribute로, 로그인 및 회원가입 페이지에서 유효성 검증에 통과하지 못하면 true 값을 받습니다.

2.1.1.2. Dynamic Routing

  • 쓰레드의 상세 페이지가 있습니다. 그러나 작성자에 따라 다른 데이터를 보여줘야 하므로 정적 라우팅만으로는 한계가 있다고 생각했습니다. 아래의 두 스크린샷은 :postId동적 라우팅을 구현한 소스 코드입니다.

2.1.2. Scss

2.1.2.1. theme switcher

  • 라이트 테마와 다크 테마를 구현하기 위해 프로젝트 세팅부터 준비했던 부분입니다. 먼저, 디자인 시스템 중 color를 정리합니다. 덧붙여 유저로 하여금 색 반전 시 위화감을 되도록 느끼지 않게 하기 위해서 transition으로 속도를 조절했습니다.
@charset "utf-8";

// other colors
$blue: #2d71f7;
$navy: #083e7f;
$red: #ff3636;

// grayscale
$grayscale-1: #000;
$grayscale-2: #999;
$grayscale-3: #ccc;
$grayscale-4: #e0e0e0;
$grayscale-5: #fafafa;
$grayscale-6: #fff;
$grayscale-7: #e6e6e6;

$pallette: (
  (lightTheme, #000, #999, #ccc, #e0e0e0, #fafafa, #fff),
  (darkTheme, #fff, #fafafa, #e0e0e0, #ccc, #999, #000)
);

@mixin ThemeTransition {
  transition:
    background-color 0.25s ease-in-out,
    color 0.25s ease-in-out;
}
  • 다음 스텝으로 theme switcher를 적용할 페이지 컴포넌트의 scss 파일을 세팅합니다.
@charset "utf-8";
@import '../../styles/partials/mixin', '../../styles/partials/theme';

@mixin theme(
  $theme,
  $grayscale-1,
  $grayscale-2,
  $grayscale-3,
  $grayscale-4,
  $grayscale-5,
  $grayscale-6
) {
  [theme='#{$theme}'] {
    @include ThemeTransition;
    background-color: $grayscale-6;
    .post-view {
      display: flex;
      flex-direction: column;
      width: 100%;
      .post-list {
        .post-item {
          padding: 0 8px 24px;
          border-bottom: 1px $grayscale-3 solid;
        }
      }
    }
  }
}

@each $theme, $grayscale-1, $grayscale-2, $grayscale-3, $grayscale-4,
  $grayscale-5, $grayscale-6 in $pallette
{
  @include theme(
    $theme,
    $grayscale-1,
    $grayscale-2,
    $grayscale-3,
    $grayscale-4,
    $grayscale-5,
    $grayscale-6
  );
  • 이런 식으로 구축하면 $pallette 안에 담긴 색상 변수명의 이름이 유저의 행동(Toggle Button Click)에 따라 반전됩니다.

2.1.2.2. mixin

  • Scss에서 mixin은 굉장히 효율적입니다. 그래서 많이 사용하는 css property set을 모아서 몇 가지를 만들었습니다. 그 가운데 몇 종류만 소개하겠습니다.
@mixin Position(
  $position,
  $top: null,
  $right: null,
  $bottom: null,
  $left: null
) {
  position: $position;
  top: $top;
  right: $right;
  bottom: $bottom;
  left: $left;
}

@mixin FlexCenter {
  display: flex;
  justify-content: center;
  align-items: center;
}

@mixin Ir() {
  overflow: hidden;
  color: transparent;
  font-size: 1px;
}

@mixin Ellipsis($multi: null, $line: null) {
  @if $multi {
    display: -webkit-box;
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp: $line; /* 라인수 */
    -webkit-box-orient: vertical;
    word-wrap: break-word;
    white-space: normal;
  } @else {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    word-wrap: break-word;
  }
}

2.1.3. Git & Github

  • 프로젝트 후반에 갈수록 지켜지지 못했으나, 프로젝트 초기에 제시했던 브랜치 전략과 네이밍이 있었습니다.
  • component 별로 담당자를 지정해 브랜치 이름과 정렬했습니다.
  • 로직이 들어가는 부분은 feature, 수정사항은 fix로 협의했습니다.

2.1.4. Etc

  • 페이지 간, 그리고 쓰레드 목록을 받아오는 과정에서 로딩이 발생했을 경우를 대비해 lottie for react로 로딩을 구현했습니다.
  • 소스 코드는 아래와 같습니다,
useEffect(() => {
    setLoading(true);
    fetch('/data/postData.json', {
      method: 'GET',
      header: {
        'Content-Type': 'application/json',
        Authorization: localStorage.getItem('accessToken'),
      },
    })
      .then(res => res.json())
      .then(data => {
        // const result = data.getThread;
        // setDataList(result);

        // mock data
        setDataList(data);
        setLoading(false);
      });
}, []);
  • fetch() 메서드로 서버 API 통신을 시도하기 전에 useState의 setter 함수인 setLoading(true)를 호출해서 로딩을 시작합니다. 모든 데이터를 성공적으로 부르면 그때 setLoading(false)를 호출해 로딩을 멈춥니다.

2.2. Back-End Tech Stack

  • Node.js
  • Express
  • MySQL
  • Bcrypt
  • JWT
  • Git & Github

3. 프로젝트 아웃트로

3.1. 한계와 다음 과제

  • 몇 가지 도전해고 싶었던 기술이 있었습니다.
  • 첫째, 쓰레드 목록을 서버에서 데이터를 받아올 때, infinite scroll을 구현하고자 했습니다. 모든 데이터를 일괄적으로 받아오는 것이 아닌 하단부에 스크롤이 닿으면 다음 데이터를 로딩하는 방식으로 UI/UX 측면이나 데이터 관리 측면에서 효율적이라 생각했으나, 기술적인 한계에 부딪혀서 다음을 기약하게 되었습니다.
  • 둘째, 추가 구현 사항으로 제시된 좋아요 토글의 서버와의 통신을 구현하고 싶었습니다. 이를 위해 Toggle Button의 클릭 여부에 따라 likeCount를 +1, -1로 보내줘야 합니다만, 일정상 구현할 수 없었습니다.

3.2. 고민과 느낀 점

  • 처음 프로젝트가 주어졌을 때, 고민했던 점은 이 프로젝트의 의도는 무엇인가?였습니다.
  • 첫 프로젝트인 만큼 동료 개발자들과의 협업이었다면, 실패한 프로젝트입니다. 불화가 있었고 제대로 된 해결책을 통해 문제를 해결하지 못했습니다.
  • 다만 프로젝트의 의도가 React의 컴포넌트 구축이라면 나름 성공했다고 생각합니다. 페이지 컴포넌트를 구성하려고 디자인 시스템 기반의 컴포넌트 라이브러리를 미약하게나마 구축했습니다. 그리고 이것은 추가 구현 화면이었던 쓰레드 수정, 쓰레드 상세 등에서도 유용하게 사용되었습니다.
profile
Front-End Developer

0개의 댓글