[2회차] 단단한 컴포넌트 만들기

gooorang·2022년 7월 5일
0

Numble

목록 보기
2/3

1회차가 끝난 후 다른 분들과 코드를 비교하면서 내가 몰랐던 부분, 놓쳤던 부분을 알게 되어 저에겐 너무너무 유용한 시간이었습니다!!
응집도를 높히고 추상화하는 것이 포인트였는데 그것이 실제로 구현된 코드를 보니 그제서야 이해가 됐습니다!
2회차도 열심히 고민해보고 다른 분들과 비교해볼 것을 생각하면 벌써 기대가 됩니다! 😆


[2회차] 단단한 컴포넌트 만들기

  • 다양한 유즈케이스에 탄력적으로 대응할 수 있는 구조에 대해서 고민해봅니다.
  • Storybook을 사용해 누구나 확인할 수 있도록 배포합니다.

탄력적인 컴포넌트란?

아래 세가지를 기준으로 생각해보기

  1. Headless 기반의 추상화하기
    : hooks을 이용해서 변하는 데이터와 UI를 분리하는 예시를 보여주심
  2. 한가지 역할만 하기
    : composition을 이용해서 하나의 역할을 하는 컴포넌트들의 조합해서 복잡한 컴포넌트를 보다 단순하게 구성하는 예시를 보여주심
  3. 도메인 분리하기
    : 🚧👷‍♂️🚧
    참고 | 토스 유튜브

고민했던 점

  • storybook, react hook form은 초면이어서 사용전 공식문서랑 사용예제를 보면서 공부했습니다.
  • styled-component나 emotion도 처음이어서 버벅거렸습니다
  • 사실 거의 모든 부분에서 막혀서 회고록 작성이 엄두가 나지 않았습니다.. ㅎ

컴포넌트 구현

Button

  • to 에 따라 버튼 또는 링크가 반환되도록 했습니다.
  • 버튼과 링크에 스타일을 한번에 전달하려고 Wrapper 안에 둘 다 감쌌습니다. 스타일드컴포넌트 방식이 아니라 클래스네임 방식이면 버튼를 먼저 리턴해서 구조를 더 깔끔하게 작성했을 것 같습니다.
  • children에 이상한 요소를 넣어서 스타일이 무너질수도 있다고 생각해서 title로 button의 글을 받도록 했습니다.
interface IButton extends ButtonHTMLAttributes<HTMLButtonElement> {
  title: string;
  to?: string;
  colored?: boolean;
}

const Button = ({ to, title, colored, ...props }: IButton) => (
  <Wrapper colored={colored}>
    {to ? (
      <Link href={to} passHref>
        <a>{title}</a>
      </Link>
    ) : (
      <button {...props}>{title}</button>
    )}
  </Wrapper>
);

Input

  • react-hook-form에 대응이 가능하도록 forwardRef를 사용했습니다.
  • 필요한 icon, isValid만 심플하게 받습니다.
  • 주신 자료에 보면 에러메세지도 있지만 조건을 충족하는지 검사하는 조건리스트도 있어서 메세지에 관한 부분은 Message라는 컴포넌트에 분리했습니다.
interface IIput extends InputHTMLAttributes<HTMLInputElement>, IMessage {
  icon?: string;
  isValid?: boolean;
}

const Input = forwardRef<HTMLInputElement, IIput>(
  ({ icon, isValid, message, validations, ...props }, ref) => (
    <Wrapper error={!!message}>
      <label>
        <span>{icon || '✉️'}</span>
        <input ref={ref} {...props} />
        {isValid && <span className='valid-icon'></span>}
      </label>
      <Messages message={message} validations={validations} />
    </Wrapper>
  )
);
  • message 에러메세지를 보여줍니다.
  • validations 조건리스트를 보여줍니다. 조건마다 완수했는지 하나씩 검사 가능합니다.
interface IMessage {
  message?: string;
  validations?: { type: string; invalid: boolean; message: string }[];
}

const Messages = ({ message, validations }: IMessage) => (
  <Box>
    {validations
      ? validations.map(({ invalid, message }) => (
          <ValidMessages invalid={invalid}>
            <span>{invalid ? 'X' : '✓'}</span>
            {message}
          </ValidMessages>
        ))
      : message && <span>{message}</span>}
  </Box>
);

Checkbox

props 이름 정하기 제일 어려웠습니다.

  • checkbox 관점에서 생각하면 어떤 역할인지 보단 어떻게 보이는지 위주로 쓰는것이 좋은 것 같다고 생각했습니다.
    isChild말고 들어쓰기 이런걸 생각해봤는데 영어로 indent라 바로 까먹을 것 같아서 제일 익숙한 단어를 썼습니다..
    isChld indent spacing..?
    description이 있다면 button을 통해 더 자세한 정보를 볼 수 있습니다.
    description moreInfo..?
interface ICheckbox extends InputHTMLAttributes<HTMLInputElement> {
  title: string;
  isChild?: boolean;
  bold?: boolean;
  description?: {};
}

const Checkbox = forwardRef<HTMLInputElement, ICheckbox>(
  ({ title, description, bold, isChild, ...props }, ref) => (
    <Wrapper bold={bold} isChild={isChild}>
      <label>
        <input type='checkbox' ref={ref} {...props} />
        <span>{title}</span>
      </label>
      {description && <button></button>}
    </Wrapper>
  )
);

CheckboxGroup

  • fieldsregister를 심플하게 받도록 구성했습니다.
  • 어떤 식으로 재사용될 진 아직 몰라서 지금 필요한 것만 넣어뒀습니다.
  • 제네틱를 이용해 다른 필드도 적용 가능하게 만들었습니다.
  • 모두 선택, 하나만 선택했을 때 처리하는 로직도 여기에 모아뒀습니다. (여기선 ... 생략됨)
interface IProps<T> {
  fields: ICheck<T>[];
  register: UseFormRegister<T>;
}

const CheckboxGroup = <T extends unknown>({ fields, register }: IProps<T>) => {
...

  return (
    <Wrapper>
      <Checkbox
        title='모두 동의합니다.'
        onChange={selectAll}
        checked={isCheckAll}
        bold
      />
      <p>
        동의에는 필수 및 선택 목적(광고성 정보 수신 포함)에 대한 동의가 포함되어
        있으며, 선택 목적의 동의를 거부하시는 경우에도 서비스 이용이 가능합니다.{' '}
      </p>
      <ul>
        {fields.map((i, index) => (
          <li key={index}>
            <Checkbox
              {...register(i.name)}
              title={i.title}
              description={i.description}
              isChild={i.isChild}
              required={i.required}
              checked={isCheck.includes(i.name)}
              onChange={selectOne}
            />
          </li>
        ))}
      </ul>
    </Wrapper>
  );
};

아쉬운 점

  • 가장 말단의 Button 컴포넌트는 가장 기본적인 내용만 알고있어야 하는데 그걸 초반에 몰라서 좀 둘러간 것
  • 컴포넌트를 잘 만들어두고 페이지 내에서 간편하게 사용해야하는데 내가 작성한 코드가 못미더워서 자꾸 파일을 왔다갔다하면서 수정한 것
  • 우선순위를 나누지 못한 것. 컴포넌트을 탄력적으로 만드는게 제일 중요했는데 페이지의 로직 구현하는데 쓴 시간을 컴포넌트에 대해서 좀 더 고민하는데 쓸 걸 하는 아쉬움.

이후에 할 일

  • 가이드를 주시면서 밑에 참고할 만한 컨텐츠를 제공해주셨는데 그게 너무 유익했습니다. 2회차 제출 후에 다시 복습하면서 제 코드와 다시 비교해보겠습니다.
    → 제출기한이 늘어서 다시 복습하고 코드를 수정해보았는데 더 많이 시도해봐야할 것 같습니다.
  • 데이터 흐름을 중단하지 말라는 자료도 그동안 제가 한 실수는 없는지 조마조마했지만 흥미롭게 읽었습니다.
  • 3회차 가이드라인도 너무 도움이 되는 주제고 할 수 있는 만큼 열심히 해보겠습니다!

0개의 댓글