{"id":15104171,"url":"https://github.com/woojoung1217/effective_component","last_synced_at":"2026-01-19T21:33:49.136Z","repository":{"id":256718485,"uuid":"856184336","full_name":"woojoung1217/Effective_Component","owner":"woojoung1217","description":"더 가치 있고 성장 가능한 컴포넌트 구현하기","archived":false,"fork":false,"pushed_at":"2024-11-08T07:52:53.000Z","size":417,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T23:51:31.437Z","etag":null,"topics":["next","react","react-hook-form","typescript-react","zustand"],"latest_commit_sha":null,"homepage":"https://effective-component.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/woojoung1217.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-12T06:20:21.000Z","updated_at":"2024-11-08T07:52:57.000Z","dependencies_parsed_at":"2024-09-18T15:45:58.714Z","dependency_job_id":"28310b0a-8bde-4aa6-afb9-8816f62c374d","html_url":"https://github.com/woojoung1217/Effective_Component","commit_stats":{"total_commits":30,"total_committers":1,"mean_commits":30.0,"dds":0.0,"last_synced_commit":"5170fb08aab343be81c9fcd57834b4720d6e8691"},"previous_names":["woojoung1217/effective_component"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woojoung1217%2FEffective_Component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woojoung1217%2FEffective_Component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woojoung1217%2FEffective_Component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woojoung1217%2FEffective_Component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/woojoung1217","download_url":"https://codeload.github.com/woojoung1217/Effective_Component/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247336145,"owners_count":20922579,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["next","react","react-hook-form","typescript-react","zustand"],"created_at":"2024-09-25T20:00:44.607Z","updated_at":"2026-01-19T21:33:49.109Z","avatar_url":"https://github.com/woojoung1217.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ✅더 가치있고 성장 가능한 컴포넌트(책임을 명확하게)\n\n지난 프로젝트에서 커스텀 훅과 공통 컴포넌트를 만들면서 몇 가지 아쉬운 점을 경험했습니다.\n대표적인 예로\n\n1.사용법이 복잡함: 컴포넌트의 사용법이 직관적이지 않고 어렵습니다. 문서화된 가이드나 설명이 부족해 처음 보는 사람들이 쉽게 이해하고 사용할 수 없습니다.\n\n2.재사용성을 고려했지만 불편함: 재사용 가능한 공용 모달을 만들었음에도 불구하고, 실제로는 해당 모달을 사용하는 것보다 순수한 HTML 태그를 사용하는 것이 더 간편하다고 느껴져 재사용성이 떨어집니다.\n\n3.디자인 변경 대응이 어려움: 컴포넌트의 스타일이 고정되어 있어, 디자인이 변경되면 수정이 어렵고 유지보수에 많은 시간이 소요됩니다. 쉽게 스타일을 커스터마이징할 수 있는 구조가 부족합니다.\n\n**[페이지로 보기]**\n\n🔗 https://effective-component.vercel.app/\n\n\u003e 이를 해결하기 위해 정한 컴포넌트 추상화 규칙\n\n# 규칙\n\n1. Headless 기반의 추상화하기\n   변하는 것 VS 상대적으로 변하지 않는 것\n2. 한 가지 역할만 하기\n   또는 한 가지 역할만 하는 컴포넌트의 조합으로 구성하기\n3. 도메인 분리하기\n   도메인을 포함하는 컴포넌트와 그렇지 않은 컴포넌트 분리하기\n\n모든 컴포넌트를 구현 할 때 최대한 고려하면서 만들기를 목표로 합니다.\n\n## 컴포넌트 카테고리\n\n⚠️ 단순 기능을 위해 만든 컴포넌트들로 디자인은 고려하지 않음.\n\n다양한 애플리케이션에서 유용하게 사용될 수 있는 공용 컴포넌트 카테고리는 다음과 같습니다:\n\n- **버튼 (Button)**\n\n  - 다양한 스타일과 기능을 갖춘 버튼 컴포넌트입니다.\n\n  - 요구사항 \u0026 고려사항\n\n| 이름       | 타입     | 기본값   | 설명                                                      |\n| ---------- | -------- | -------- | --------------------------------------------------------- |\n| `text`     | `string` | -        | 버튼 안에 들어갈 텍스트                                   |\n| `styleID`  | `string` | -        | 각기 다른 스타일 지정 위함, 스타일은 `.module.css`에 지정 |\n| `onClick`  | `func`   | -        | 버튼이 클릭될 때 호출될 이벤트 핸들러 지정                |\n| `disabled` | `bool`   | `false`  | 버튼 비활성/활성 여부를 결정하는 부울 값                  |\n| `type`     | `string` | `button` | 버튼의 타입을 지정 (`button`, `submit`, `reset`)          |\n\n- 문제점\n  variant로 전달된 값이 styles[variant]에서 찾을 수 없는 경우, 빈 문자열로 대체 되도록 설계 하지만 variant에 예상치 못한 값이 전달되면 스타일이 적용되지 않아서 예상치 못한 결과가 나올 수 있습니다.\n\n- 해결\n  variant에 대한 유효성 검사를 추가하거나, 미리 정의된 값으로만 스타일을 적용하도록 제한하는 방식으로 해결\n  variant에 대해 타입을 사용해 명시적인 제한을 두도록 변경 했습니다.\n\n\u003chr/\u003e\n\n- **모달 (Modal)**\n\n  - 화면 중앙에 팝업 창을 띄우는 컴포넌트입니다.\n\n  - 요구사항 \u0026 고려사항\n    접근성 문제: 모달이 다른 요소들과 DOM 구조를 공유하게 되면 포커스 트래핑(focus trapping)과 같은 접근성 기능을 구현하는 것이 어려울 수 있습니다. 포탈을 사용하면 모달이 별도의 DOM 트리에서 관리되어, 키보드 내비게이션이나 화면 판독기와 같은 기능을 더 쉽게 제어할 수 있습니다.\n\n    CSS 스타일링 충돌: 모달을 구현하는 부모 컴포넌트의 스타일이 모달 내부로 전파될 수 있습니다. 모달은 일반적으로 독립적인 스타일링이 필요하지만, 포탈 없이 구현하면 상위 컴포넌트의 스타일이 의도치 않게 영향을 미칠 수 있습니다.\n\n    모달의 열고 닫힘 상태 : 항상 모달을 사용하기 위해서 사용하는 부분에서 open, close 함수를 만들어야 했는데 모달의 상태를 전역 상태 라이브러리로 저장하고 관리 하도록 합니다. (zustand)\n\n| 이름                   | 타입              | 기본값  | 설명                            |\n| ---------------------- | ----------------- | ------- | ------------------------------- |\n| `isOpen`               | `bool`            | -       | 모달의 상태                     |\n| `onClose`              | `func`            | -       | 모달의 닫기 함수                |\n| `title`                | `string`          | -       | 모달에 들어 갈 제목             |\n| `children`             | `React.ReactNode` | `false` | 모달의 내부 콘텐츠              |\n| `closeOnBackdropClick` | `bool`            | `click` | 모달의 뒷요소 클릭 시 닫기 여부 |\n\n- 해결\n  포탈을 사용하면 모달이 부모 컴포넌트의 DOM 계층에 영향을 받지 않고, z-index 문제나 레이아웃 문제를 쉽게 해결할 수 있었습니다.\n\n\u003chr/\u003e\n\n- **툴팁 (ToolTip)**\n\n  - 버튼 혹은 요소에 클릭 했을 시 메시지를 보여주는 컴포넌트입니다.\n\n  ```jsx\n  // 툴팁 인터페이스\n  interface Props {\n    title: string;\n    tooltipItem: string;\n    triggerEvent?: \"hover\" | \"click\"; // 동작 방식 유연성 추가\n    position?: \"top\" | \"bottom\" | \"left\" | \"right\"; // 위치 지정 가능성 추가\n  }\n  ```\n\n- **입력 필드 (Input Field)**\n\n  - 텍스트, 비밀번호 등 다양한 형태의 입력 필드를 관리하는 컴포넌트입니다.\n\n  ```jsx\n  interface InputProps {\n    name: string;\n    label: string;\n    type?: string;\n    placeholder?: string;\n    required?: boolean;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    validation?: any;\n    error?: FieldError;\n  }\n  ```\n\n- **폼 (Form)**\n\n  - 폼의 레이아웃과 유효성 검사 등을 처리하는 컴포넌트입니다.\n  - react-hook-form 라이브러리를 사용해 각 인풋에 대한 validation을 효율적으로 관리 할 수 있도록 개선했습니다.\n\n- **퍼널 (funnel)**\n\n  - 토스에서 사용하는 설문조사 패턴을 사용하는 컴포넌트입니다.\n  - 전역 상태를 사용하지 않고 다양한 입력들을 한 페이지에서 처리 할 수 있습니다. (응집도)\n  - 디자인 변경이나 컴포넌트 변경에 빠르게 대처 할 수 있습니다.\n\n  - 개선\n    기존 방식으로는 단일 URL로 브라우저가 제공하는 뒤로가기 (히스토리) 사용이 어려웠으나\n    next 의 Navigation 기능을 사용해 해결 했습니다.\n\n- **알림/토스트 (Alert/Toast)**\n\n  - 사용자에게 알림 메시지를 제공하는 컴포넌트입니다.\n\n  - 개선\n    zustand 를 사용한 전역 상태관리로 알림의 메시지,타임아웃,삭제 등 관리하도록 구현 했습니다.\n    이후 간단하게 어느 페이지에서든 간단하게 사용 할 수 있도록 개선 했습니다.\n\n- **드롭다운 (Dropdown)**\n\n  - 선택 가능한 항목을 드롭다운 목록으로 표시하는 컴포넌트입니다.\n\n  - 개선\n    드랍다운 된 이후 내용은 잘 변경 되지만 드랍다운 밖 요소를 눌렀을 때 드랍다운이 닫히지 않는 오류가 있음\n    이를 개선하기 위해 useDetectRef hooks 을 구현\n\n  ```jsx\n        /\\*_ ref 외부 요소를 눌렀을 때 드랍다운을 닫도록 도와주는 함수 _/\n\n    const useDetectRef = (ref: RefObject\u003cHTMLElement\u003e): [boolean, React.Dispatch\u003cReact.SetStateAction\u003cboolean\u003e\u003e] =\u003e {\n      // dom object인 ref 를 인수로 받아온다\n    const [isOpen, setIsOpen] = useState(false);\n     // 열림상태 default = false\n\n        useEffect(() =\u003e {\n        // 화면에 클릭 요소가 일어나면\n        const handleClick = (e: MouseEvent): void =\u003e {\n        if (ref.current \u0026\u0026 !ref.current.contains(e.target as Node)) {\n          // 현재 ref요소가 있고 클릭된 요소를 비교해서 포함하고 있지 않으면\n        setIsOpen(!isOpen);\n          // 화면을 닫도록 =\u003e 화면 밖을 누른다는 의미\n        }\n        };\n\n        if (isOpen) {\n          window.addEventListener(\"click\", handleClick);\n          // 이벤트 등록\n        }\n\n        return () =\u003e {\n          window.removeEventListener(\"click\", handleClick);\n        };\n\n    }, [isOpen, ref]);\n\n    return [isOpen, setIsOpen];\n    };\n\n    export default useDetectRef;\n  ```\n\n  이 후 옵션의 갯수가 유동적으로 변할 수 있어 사용 하는 부분에서 options 프롭을 string [] 로 받도록 변경\n\n- **카드 (Card)**\n\n  - 정보나 컨텐츠를 카드 형식으로 표시하는 컴포넌트입니다.\n\n    ```jsx\n    interface CardProps {\n      title: string;\n      image: string;\n      description: string;\n      link?: string;\n    }\n    ```\n\n- **알림 (alert)**\n\n  - 애플리케이션의 네비게이션을 위한 바 컴포넌트입니다.\n\n- **사이드바 (Sidebar)**\n\n  - 애플리케이션의 사이드 메뉴를 제공하는 컴포넌트입니다.\n\n- **파일 업로드 (File Upload)**\n\n  - 파일을 업로드할 수 있는 컴포넌트입니다.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoojoung1217%2Feffective_component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwoojoung1217%2Feffective_component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoojoung1217%2Feffective_component/lists"}