[유데미x스나이퍼팩토리] 10주 완성 프로젝트 10일차 - Component: Thumbnails 개발

TK·2023년 7월 26일
0
post-thumbnail
  • 일시 : 23.07.24 월요일 ~ 07.26 수요일
  • 주제 : 통증관리 시스템 - 분류 3.포인티 디자인 시스템 라이브러리 개발

오늘의 목표


  • 분배된 역할 중 Component 페이지에서 Thumbnails 부분을 개발을 마무리하고 pull reqeust를 보낸다.

진행 사항


✅: 완료, 🌀: 진행중, 🆕: 대기

🆕 (개인) Component: Banner 개발

  • 두 가지 타입의 배너 개발
  • props를 얼만큼 받아야할지 관련하여 멘토님께 질문

🌀 (개인) Component: Thumbnails 개발

  • 운동 수행 영상/이미지 썸네일 상태를 보여줄 수 있는 컴포넌트 개발한다.
  • 영상/이미지에 따라서 타입을 나눈다.
  • 상태에 따라 스타일을 다르게 설정한다.
  • 필수 prop로 src을 받는다. 이외 따로 타입을 지정하지 않을 경우 normal타입으로 지정한다.

코드 보기 (추후 수정 가능성 있음)

  • 파일구조

    • Components
      • Thumbnails
        ├─ Image
        │ └─ index.tsx
        ├─ Video
        │ └─ index.tsx
        ├─ index.tsx
        ├─ styles.ts
        └─ type.ts
  • Image/index.tsx

// 생략 (Video/index.tsx 참고)
  • Video/index.tsx
import Icon from '@lib/foundation/Icon';
import * as St from '../styles';
import { IVideo } from '../type';

const VideoThumbnails = ({ state = 'normal', type = 'video', src, runningtime = 0 }: IVideo) => {
  const runningTimeMin = runningtime && String(Math.floor(runningtime / 60000));
  const runningTimeSec = (runningtime && String(Math.floor((runningtime / 1000) % 60)).padStart(2, '0')) || '00';

  return (
    <St.ThumbnailContainer src={src} state={state}>
      <St.VideoRunningTime>
        {runningTimeMin}:{runningTimeSec}
      </St.VideoRunningTime>
      <St.BackgroundColor state={state} />

      {state === 'delete' && (
        <St.DeleteState>
          <Icon name='thumbnails_deletecircle' />
        </St.DeleteState>
      )}
      {state === 'select' && (
        <St.SelectState>
          <Icon name='thumbnails_checkcircle' />
        </St.SelectState>
      )}
      {state === 'error' && (
        <St.ErrorState>
          <Icon name='thumbnails_errorcircle' />
          <p>원본 파일 삭제됨</p>
        </St.ErrorState>
      )}
      {state === 'video_play' && (
        <St.PlayState>
          <Icon name='thumbnails_playcircle' />
        </St.PlayState>
      )}
    </St.ThumbnailContainer>
  );
};

export default VideoThumbnails;
  • styles.ts
import styled from 'styled-components';
import { IImage, IVideo } from './type';

export const ThumbnailContainer = styled.div<IVideo | IImage>`
  position: relative;

  width: 100px;
  height: 100px;

  margin: ${({ state }) => (state === 'delete' ? '10px 10px 0 0' : null)};

  border-radius: 4px;
  background-image: url(${(props) => props.src});
  background-size: cover;
  background-position: center;

  color: var(--Bg_300);
  font-size: 0.75rem;
`;

export const VideoRunningTime = styled.div`
  /* 생략 */
`;

export const BackgroundColor = styled.div<IVideo | IImage>`
  position: absolute;

  width: 100%;
  height: 100%;

  border-radius: 4px;
  opacity: 0.6;

  /* state에 따른 backgorund 색상 변경 */
  background-color: ${({ state }) => {
    switch (state) {
      case 'select':
        return 'var(--Pri_500)';
      case 'video_play':
        return 'var(--Dim)';
      case 'error':
        return 'var(--Dim)';
      default:
        return '';
    }
  }};

  cursor: ${({ state }) => (state === 'video_play' ? 'pointer' : null)};
`;

// 이하 생략
  • type.ts
export interface IVideo {
  type?: 'video';
  src?: string;
  state?: VideoState;
  runningtime?: number;
}

export interface IImage {
  type?: 'image';
  src?: string;
  state?: ImageState;
}

type VideoState = 'normal' | 'delete' | 'select' | 'video_play' | 'error';
type ImageState = 'normal' | 'delete' | 'select' | 'error';

적용코드

// ex)
<ImageThumbnails src={{/*이미지 url*/}} state='error'/>
<VideoThumbnails src={{/*이미지 url*/}} runningtime={5685263} state='delete'/>

적용화면

회고


state별로 나뉘는 타입 처리하기

이번 썸네일 컴포넌트에서 이미지와 영상의 차이점이라면 영상은 running time이 있고, 상태 중에 play버튼을 누를 수 있는 상태가 있다는 것이다. 따라서 두 가지 차이점 빼고는 공통점이 너무 많아서 최대한 코드를 줄일 수 있지 않을까 했다. 또한 아이콘만 다르고 (삭제 빼고) 모두 가운데 위치하는 것도 같아서 그것도 상태별로 props를 받아서 조작할 수 있지 않을까 하면서 코드를 짜려니까 굉장히 복잡하고 고민하는 시간이 오래 걸렸다.

처음엔 이미지와 영상을 분리하지 않고 하나의 컴포넌트로 조건(이미지/영상)을 바꾸면서 바뀐 조건에 따라 타입도 바꿔서 적용시키고자 했는데 쉽지 않았다.

(위 그림처럼 video일 땐 VideoState가 적용되고 image일 땐 ImageState가 적용되도록 하고싶었다.)

이와 관련하여 '조건부 타입'이라는 타입스크립트의 활용법을 찾아보았데 적절히 적용시킬 방안을 찾지 못했고 결국 두 타입으로 나누기로 했다. 두 타입으로 나뉨으로써 두 개의 컴포넌트를 만들었다. 두 컴포넌트 (ImageThumbnail / VideoThumbnail)는 참조하는 interface (IImage / IVideo)가 다르지만 각 상황별에 따른 style은(normal/delete/select/error) 같기 때문에 같은 styled component를 참조하도록 했다.

지금은 이런 방식으로 작동하지만 나중에 시간을 들여 더 고민하여 클린코드로 리팩토링 하고자 한다.



길잡이


Typescript 조건부 타입

참고 레퍼런스


String.prototype.padStart()

millisecond로 runningtime 값을 전달 받은 후 이를 분:초 형태로 나타낼 때, 초 단위에는 통상적으로 두 자릿수로 나타내기 때문에 초 단위가 1초여도 앞에 0을 붙여준다. (ex) 10:01) 따라서 이 내장 함수를 이용하였다. (* String에만 사용할 수 있음에 유의한다.)

참고 레퍼런스


p태그 margin

Error타입의 문구를 작성하기 위해 p태그 사용했는데 내가 설정하지 않은 margin이 나와서 '뭐지' 싶었다. 평소에 reset css를 설정해 놓아서 p태그의 특징을 잊고 있었다.
이 기회에 p태그의 특징을 다시 한번 익히게 되었다.

  • p태그의 p는 paragraph의 약자로 뜻 그대로 문단을 나타낼 때 쓰인다.
  • p태그는 기본적으로 block의 성격을 가진 태그로 내부에서 텍스트의 정렬을 위해서는 text-align 속성을 사용해 텍스트 정렬을 한다.

p태그 사용의 유의점

  • p태그는 기본적으로 margin의 상하값에 간격이 들어가있다.
    그렇기 때문에 css를 초기화하거나 p태그에 margin:0 값을 넣어주어야 한다.
  • p태그는 블록 요소의 태그이긴해도 p태그 안에(자식요소로) 다른 블록 요소가 들어가지 못한다. 그리고 무엇보다 p태그 안에 p태그는 삽입이 불가능하다.
<p>
   <h2>제목</h2>  <!-- x -->
   테스트를 위한 텍스트
</p>

<p>
   p태그 안에 또 p태그를..?
   <p>가능할까..??</p>   <!-- x -->
</p>

참고 레퍼런스



본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

profile
쉬운게 좋은 FE개발자😺

0개의 댓글

관련 채용 정보