Effective Component 지속 가능한 성장과 컴포넌트

Tags
토스
Property
발표자
한재엽
 

도입

컴포넌트와 관련된 여러 고민을 했다
 
제품은 변화를 겪으면서 성장한다.
변경이 발생했다는 것은, 놓쳤던 고객의 니즈를 발견한 것이 아닐까?
어떻게 빠르게 그것을 반영할 수 있을까?
 
무엇이 변경될 지 알 수 있었다면 처음부터 그렇게 만들었을 테지만, 우리는 그것을 알 수 없다.
변경은 예측할 수 없다.
변경은 예측이 아닌 대응을 해야한다.
 

지난 날을 되돌아보기

어떤 기준으로 코드를 분리해왔는지?
  • 중복
  • 코드가 커서
명확한 기준, 설계 없이 이렇게 막연한 이유로 코드를 나누게 되는 것이 문제라고 생각한다.
 
코드를 봤을 때
  • 어떤 컴포넌트인지
  • 어떤 역할을 하는지
  • 어떤 생각으로 만들었고
  • 어떻게 사용할 수 있는지
위 항목들이 명확하게 보이지 않는다면 한번 생각해 봐야한다.
 

컴포넌트 잘 나눠보자

1. Headless 기반의 추상화하기

첫번째, Headless 기반으로 추상화가 잘 이뤄져야 한다.
변하는 것 vs 상대적으로 변하지 않는 것
Headless : 관심사를 분리하여 스타일링을 담당하게 되는 부분을 과감히 제외하고, 데이터와 관련된 부분에만 집중하여 모듈화 하는 것
Separation of Concerns (관심사의 분리)
 
notion image
컴포넌트는 크게 3가지 역할을 한다.
  1. 데이터 관리
      • 외부에서 주입 받은 데이터
      • 상태와 가은 내부 데이터
  1. UI (User Interface)
      • 데이터가 사용자에게 어떻게 보여질 지를 정의
  1. 상호작용
      • UI를 기반으로, 사용자와 어떻게 상호작용 할 지를 정의

예시1. 버튼

notion image
  1. 데이터
      • state라는 내부 상태 정의
  1. UI
      • 버튼 on/off 라는 텍스트 노출
  1. 상호작용
      • onClick을 통해 클릭 상호작용
 

예시2. 달력

데이터는 크게 바뀌지 않지만, UI는 자주 바뀔 수 있는 경우, 데이터와 UI를 분리해보면 좋다.
대표적인 예시가 달력으로, 달력에 사용되는 데이터는 바뀌지 않지만, 달력을 표현하는 UI는 언제든 바뀔 수 있다. 따라서 이를 분리해서 만들어 보자.
  • 달력에 필요한 데이터를 반환해주는 훅 생성 (불변)
    • 달력 표현에 필요한 데이터를 반환함
    • 현재의 Month, Month에 대한 Days,
  • 달력을 표현하는 UI 작성 (가변)
    • hooks에서 반환받은 데이터를 어떻게 표현할 지만 정의하면 됨.
    • UI를 바꾸고 싶다면, 여기만 바꾸면 됨
notion image
notion image
컴포넌트에선 상호작용은 useLongPress hooks가 반환하는 값을 UI에 적용하기만 하면 되고, 이제는 어떻게 보일 지에만 집중할 수 있게 되었다.
 
여태까지 hooks로 모듈화 하는 얘기만 했는데, 각 모듈이 한 가지 일만 하는 것이 무엇보다 중요하기 때문이다.
 

2. Composition

한 가지 역할만 하거나, 그 조합으로 구성하기
버튼 처럼 단순한 컴포넌트는 하나로만 구성해서 쓰겠지만, 복잡한 컴포넌트는 작은 컴포넌트들의 조합(Composition)으로 구성하게 된다.

예시1. ReactFrameworkSelector

notion image
위 코드는 변경이 발생했을 때 제대로 대응하지 못하며, 그렇기에 재사용하기도 어렵다. ( 종속성이 너무 강하다는 것이 문제 )
  • label도 프롭으로 받을 수 있도록 해야겠고,
  • InputButton이 아닌 다른 컴포넌트를 쓰고 싶을 수도 있으니 프롭으로 받자
 
notion image
notion image
  1. 데이터 분리
      • isOpen : Menu의 노출 여부를 제어하는 내부 상태(데이터)
      • isOpen 상태는 Dropdown 이라는 컴포넌트로에서 관리
  1. UI 분리
      • isOpen 상태에 따라 보여질 드롭다운 메뉴
        • Dropdown.Menu
      • 메뉴를 구성하는 각각의 버튼
        • Dropdown.Item
  1. 상호작용 분리
      • 드롭다운이 눌러졌을 때
        • Dropdown.Trigger
      • 드롭다운 메뉴 구성하는 각각의 버튼이 눌러졌을 때
        • Item.onClick → Dropdown.onChange
개선 결과
개선 결과
Select 컴포넌트와 trigger 프롭으로 전달한 InputButton은 서로의 존재를 알지 못한다.
서로의 변경이 서로에게 영향을 끼치지 않게 된 것인데, 이것은 각각의 컴포넌트가 변경에 유연하도록 만든다.
💡
이렇게 합성 가능하도록 컴포넌트를 설계하면, 재사용하기 좋고 확장하는 데에도 좋다.
 

예시2. 버튼 클릭 시 별도 모달 뜨는 컴포넌트

notion image
 

3. 도메인 분리하기

도메인을 포함하는 컴포넌트와, 그렇지 않은 컴포넌트를 분리하기
 
컴포넌트를 주입받은 것처럼, 데이터도 주입받으면 어떨까?
언제 데이터를 주입받고, 언제 데이터를 스스로 가져와야 할까?
 
notion image
앞서 봤던 예시와 비슷한 UI가 주어졌다. 기존의 컴포넌트를 재사용해서 만들 수 있다면 참 좋을 거 같다.
위 UI에 앞서 봤던 FrameworkSelect 컴포넌트를 바로 재사용해서 만들 수 있을까? 딱 봐도 어렵다. 왜 그럴까?
처음부터 프레임워크라는 도메인을 분리했으면 어땟을까?
도메인 맥락을 제거하고, 일반적인 인터페이스로 변경
도메인 맥락을 제거하고, 일반적인 인터페이스로 변경
  • 일반적인 이름일 수록 이해하기 쉽다.
💡
컴포넌트의 이름과 Props의 이름들을 일반적으로 짓는 것이 중요하다.
💡
컴포넌트 인터페이스가 표준에 가까울수록 이해하기 쉬워진다.
 

바로 해볼 수 있는 액션아이템

  1. 인터페이스를 먼저 고민하기
구현해야하는 기능이 이미 만들어져있다고 가정하고, 이것을 사용한다고 가정하고, 코드를 작성해 본다.

키 포인트

  • 의도가 무엇인지가 잘 드러나는지?
  • 이 컴포넌트의 기능이 무엇인지가 잘 드러나는지?
  • 어떻게 표현되어야 하는지가 잘 드러나는지?
위 내용이 잘 드러나도록 설계하는 것이 구현 코드보다 중요하다.
 
  1. 컴포넌트를 나누는 이유를 상기하기
  • 복잡도를 낮추기 위함인지
  • 재사용을 위한 코드인지
  • 꼭 분리해야하는 코드인지
잘 만들어둔 컴포넌트는 미래의 나에게 도움이 된다.
빠른 속도의 개발에 필수적이다.