ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [넘블챌린지] 컴포넌트 만들기
    카테고리 없음 2022. 7. 14. 02:00

    Button 컴포넌트

    만들고자 하는 Button 컴포넌트가 활용되는 방식은 아래와 같습니다..

    1. 일반적인 button element
    2. form의 submit
    3. a tag를 이용한 페이지 라우팅 (next/link 사용)

    Button Component를 만들면서 저는 a tag를 이용한 페이지 라우팅 기능을 포함시키도록 하는 것이 엄청 어려웠습니다. 아래의 코드와 같이 저는 as prop을 통해 as prop에 "div"를 작성하면 div 엘리먼트로 변환이 가능하고, as prop으로 "a"를 주입하면 a tag가 되도록 변환하길 원했습니다. 이런 기능이 가능한 component를 polymorphic component라 하는 것을 이번에 처음 배웠습니다.ㅎㅎ 그런데 타입스크립트와 같이 사용하는 경우 까다로워져서 여러 블로그를 참고해보아도 아예 타입스크립트가 먹통이 되는 경우가 많았습니다... 여기저기 검색해보니 어떤 개발자분이 블로그에 polymorphic component와 타입스크립트에 대해서 엄청 자세히 작성해주셨는데 (리액트와 ), 해당 블로거분의 코드를 많이 차용했습니다...

     

    덕분에 타입스크립트를 살릴 수 있었지만 2가지 문제점이 발생했는데,

     첫번째는 displayName prop에 접근이 안된다는 점입니다. 각 컴포넌트는 displayName을 작성할 수 있는데, 이는 만약에 에러가 난 경우 어느 컴포넌트에 문제가 생겼는지를 displayName prop을 지칭해서 메시지를 나타낸다고 합니다. 만약에 displayName이 없는 경우 그냥 Component로 표시되어 디버깅에 문제를 일으킬 수 있습니다. Nextjs에서 a tag를 통해 하이퍼링크로 이동하고자 할 때 아래와 같이 'next/link'에서 Link를 import하여 a tag를 둘러쌓아 사용합니다. 그런데 제가 사용하는 것과 같이 특정 컴포넌트로 둘러쌓아야 할 때는 해당 component에 ref를 전달할 수 있도록 React.forwardRef()를 작성해야하는데, 이를 작성하는 경우 제가 잘 몰라서 그런지 displayName을 직접 작성해야합니다... 그런데 이것이 막혀서 해결을 못했습니다...

     두번째는 storybook에 사용하는 prop들이 원활하게 나타나지 않는다는 점입니다. size의 타입을 "small", "medium" 등 특정 값들을 포함하도록 주어도 이게 명확하게 나타나지 않았습니다.

     

    지금 생각해보면 애초에 Button 컴포넌트를 a tag 컴포넌트와 button 컴포넌트로 분리시키고 두 컴포넌트가 같은 prop과 style을 같이 사용하는 경우로 작성했으면 어땠을까 싶습니다...

    import Link from "next/link";
    
    // 버튼 컴포넌트와 nextjs의 Link를 같이 사용할 수 있도록 작성했습니다.
    <Link href="/auth/signup" passHref>
        <Button as="a" size="small">
          Signup
        </Button>
    </Link>

     

    Input 컴포넌트

    Input 컴포넌트의 요구사항은 다음과 같습니다.

    1. react-hook-form의 register를 사용할 수 있어야 한다.
    2. focus시 border-bottom이 파란색으로, error시 빨간색으로 바뀐다.
    3. 좌측에 icon이 표시된다.
    4. invalid한 값 입력 시 적절한 에러 메세지를 보여준다.
    5. 필요 시 autoComplete도 사용할 수 있다. (autoComplete은 구현 못했습니다...)

    Input 컴포넌트를 작성하면서 가장 고민했던 점은 어떻게 use-hook-form 라이브러리와 함께 효율적으로 작성할 수 있는지였습니다. use-hook-form 을 사용해보니 매우 편리했습니다! register을 통해 input에 여러가지 정보들을 쉽게 주입할 수 있었으며, watch를 통해 해당 input값의 변화를 쉽게 알 수가 있습니다. 그리고 이외에 여러가지 편리한 기능을 제공하여 상당히 만족스러웠습니다. 

     

    아래 코드를 보시면 register로 생성한 정보를 input 엘리먼트에 쉽게 주입할 수 있어서 사용하기 편했습니다.

    const {
        watch,
        register,
        handleSubmit,
        setValue,
        formState: { errors },
      } = useForm<register>({ criteriaMode: "all" });
    
    // register 생성 예시
    const password = register("password", {
        validate: {
          combinationCheck: (password) => {
            let count = 0;
            const error = "영문/숫자/특수문자 2가지 이상 조합 (8~20자)";
    
            if (password.length < 8 || password.length > 20) return error;
            if (password.search(/[0-9]/g) != -1) count++;
            if (password.search(/[a-z]/gi) != -1) count++;
            if (password.search(/[!@#$%^&*()?_~]/g) != -1) count++;
            if (count < 2) return error;
            return true;
          },
          duplicateCheck: (password) => {
            const error = "3개 이상 연속되거나 동일한 문자/숫자 제외";
            if (/(\w)\1\1/.test(password)) return error;
            return true;
          },
          emailCheck: (password) => {
            const error = "아이디(이메일) 제외";
    
            if (emailWatch === password) return error;
            return true;
          },
        },
    });
    
    // Input 컴포넌트 사용 예시
    <Input
        label="Enter password check"
        svg="lock-open"
        register={passwordCheck}
        name="passwordCheck"
        type="text"
        placeholder="비밀번호 확인"
        errors={errors?.passwordCheck?.types}
    />
    
    // input 엘리먼트에 해당 register 데이터 주입
    <input type={type} {...register} />

     

    다만 에러에 관련하여 다큐먼트에 자세히 작성되있지 않아 어떻게 사용하는지 이해하는데 좀 걸렸습니다...

     

    use-hook-form

    https://react-hook-form.com/get-started/

     

    Get Started

    Performant, flexible and extensible forms with easy-to-use validation.

    react-hook-form.com

     

    Checkbox 컴포넌트 / CheckboxGroup 컴포넌트

    Checkbox의 요구사항입니다.

    1. react-hook-form의 register를 사용할 수 있어야 한다.
    2. font bold 처리
    3. description 유무
    4. 포함관계 등의 요구사항 (실패...)

    마지막 포함관계 요구사항의 정보를 포함시키도록 하는데에 실패했습니다. Signup 페이지에 포함관계를 나타내는데에는 성공했지만 이를 컴포넌트로 추상화하는데에는 실패했습니다...

     

    위의 관계를 트리구조로 나타냈으며, 만약에 자식 노드를 갖고 있는 노드를 클릭하면 아래의 모든 자식 노드들의 value 값이 변경될 수 있도록 dfs를 활용했습니다.

     

    특수한 관계를 지니는 경우 useEffect()를 활용하여 해당 관계들에 알맞는 값의 변화를 주기도 했습니다. 위와 같이 "모두 동의합니다" 노드의 자식 노드 중에 하나라도 false가 존재한다면 "모두 동의합니다"의 값이 false가 되도록 했으며, "광고성 정보 수신 동의" 노드의 자식 노드 중에 하나라도 true가 존재한다면 "광고성 정보 수신 동의" 노드의 값 또한 true가 되도록 했습니다.

    // 트리 구조
    const relationshipDefault: relationshipType = {
        allAgree: {
          master: [],
          sub: [
            "terms_fourteen",
            "terms_service",
            "terms_commerce",
            "terms_privacy_collect_use",
            "terms_privacy_collect_use",
            "agree_to_collect_third_part_information",
            "agree_to_collect_for_ads",
          ],
          value: false,
        },
        terms_fourteen: { master: ["allAgree"], sub: [], value: false },
        terms_service: { master: ["allAgree"], sub: [], value: false },
        terms_commerce: { master: ["allAgree"], sub: [], value: false },
        terms_privacy_collect_use: { master: ["allAgree"], sub: [], value: false },
        agree_to_collect_third_part_information: {
          master: ["allAgree"],
          sub: [],
          value: false,
        },
        agree_to_collect_for_ads: {
          master: ["allAgree"],
          sub: ["advReceive"],
          value: false,
        },
        advReceive: {
          master: ["agree_to_collect_for_ads"],
          sub: [
            "agree_to_receive_email",
            "agree_to_receive_sms",
            "agree_to_receive_push",
          ],
          value: false,
        },
        agree_to_receive_email: { master: ["advReceive"], sub: [], value: false },
        agree_to_receive_sms: { master: ["advReceive"], sub: [], value: false },
        agree_to_receive_push: { master: ["advReceive"], sub: [], value: false },
      };

    Github: https://github.com/dohpark/coupangclone/tree/stage2

    Login Page: https://coupangclone.herokuapp.com/auth/login

     

    https://coupangclone.herokuapp.com/auth/login

     

    coupangclone.herokuapp.com

    Signup Page: https://coupangclone.herokuapp.com/auth/signup

     

    https://coupangclone.herokuapp.com/auth/login

     

    coupangclone.herokuapp.com

    Storybook: https://www.chromatic.com/library?appId=62cea3fe353a034cb10ccc17&branch=stage2

     

    Library • dohpark/coupangclone

     

    www.chromatic.com

     

Designed by Tistory.