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

Bewell·2023년 8월 29일
1

토스의 한재엽 개발자님의 영상을 써머리한 문서입니다. 원본은 https://toss.im/slash-22/sessions/1-7 에서 확인할 수 있습니다.

변경에 대응하기

컴포넌트란?

  • 데이터를 주입하고, 어떻게 데이터를 보여줄지 UI를 보여주고, 사용자와 어떻게 상호작용할 지를 정의

달력 컴포넌트를 만든다고 가정하자.
달력을 구성하는 데이터는 변하지 않지만,
달력의 UI는 언제든 바뀔 수 있다.
그래서 데이터와 UI를 분리해야 한다

2x2배열의 데이터로 추상화 - 배열의 요소는 Date객체
현재의 날짜 데이터를 추상화

useCalendar hooks을 통해 데이터를 추상화했고, Calendar컴포넌트는 달력의 UI를 어떻게 보여줄 지만 정의하면 된다
추후 달력의 UI가 변경되더라도 달력에 대한 데이터를 갖고 있는 useCalendar hooks를 가져다 쓰면 된다.


UI를 관심사에서 제외할 수 있다
오로지 데이터만 집중해서 모듈화 할 수 있는데 이런 패턴을 Headless 라고 한다
한 가지 문제만 집중하기 때문에 더 많은 곳에서 사용할 수 있고, 다른 변경으로부터 격리시킬 수 있다


지금까지 데이터를 추상화 했다면, 이번엔 상호작용하는 부분을 파헤쳐보자.
이번에도 UI와 분리해보자

  1. 버튼 컴포넌트에 longPress라는 동작을 정의하려고 한다
  2. 왼쪽 코드처럼 여러가지 로직이 들어가게 되는데, 이렇게 되면 컴포넌트 내부가 복잡해진다
  3. 위에서 데이터를 추상화했던것 처럼, 상호작용부분도 따로 추상화 할 수 있다
  4. 오른쪽 코드처럼 useLongPress hooks를 만들었다
  5. 컴포넌트에서는 hooks로 반환되는 값을 UI에 적용하기만 하고, 이젠 어떻게 보여질지에만 집중할 수 있게 되었다
  6. 이렇게 추상화 해놓으면 longPress기능이 필요한 컴포넌트에서 가져다 쓸 수 있다

지금까지 컴포넌트 이야기보다는 Hooks로 모듈화하는 이야기만 했는데, 변경에 유연해지려면 각 모듈이 한가지 일만하는게 중요하다


2. Composition

이번엔 한가지 역할만 하는 컴포넌트들의 조합으로 구성되어야 하는 Composition에 대해 알아보자

Button과 같은 단순한 컴포넌트는 한 가지 역할만 잘 수행했지만, 복잡한 컴포넌트는 어떻게 접근하면 될까?

흔하게 볼수 있는 Select UI다
이 UI를 만들다 보면 자연스럽게 작은 컴포넌트들을 사용하여 구성하게 된다

지금 구조는 변경이 일어났을때 제대로 대응하지 못한다. 그렇기 때문에 여러곳에서 재사용하기도 어렵다.

만약 이 컴포넌트를 다른 곳에서 사용한다면 어떨까?

InputButton 대신에 다른 컴포넌트를 사용하고 싶을땐 어떻게 하면 될까?
이 한줄만 봐도 변경에 유연하지 못하다는 문제를 확인 할 수 있다

다루고 있는 역할을 기준으로 분리해본다

Select컴포넌트와 InputButton 컴포넌트는 서로의 존재를 모르고, 서로의 변경이 서로에게 영향을 끼치지 않는다
이것은 각각의 컴포넌트가 변경에 유연하도록 만든 것
trigger부분이 변경되더라도 Select컴포넌트 자체는 아무 변경, 영향이 없다

이렇게 합성 가능하도록 컴포넌트를 설계하면 재사용하기 좋고 확장하기도 좋다


UI만 봐서는 꽤 복잡한 컴포넌트로 생각할 수 있지만, 이 컴포넌트가 관리하는 데이터 관점에서 바라본다면 복잡하지 않다

옵션을 제공하고 선택한 값을 보여주는 컴포넌트라고 생각한다면, 이전의 Select컴포넌트와 비슷하다
즉 표현되는 UI는 다르지만, 인터페이스는 비슷하게 구성할 수 있다

Select 컴포넌트와 비슷한 인터페이스로 구성할 수 있다

Headless 기반으로 추상화를 하고 조합으로 컴포넌트를 표현하는 방법을 알아보았다


3. 도메인 분리하기

이번엔 레이어링 관련된 이야기 해보자
모든 컴포넌트에서 데이터에 접근할 수 있지만, 컴포넌트를 주입받은 것처럼 데이터도 주입받으면 어떨까?

  • 언제 데이터를 주입받고?
  • 언제 스스로 데이터를 가져와야 할까?

위와 같이 새롭게 구현할 UI가 생겼는데, 앞서 살펴본 FrameworkSelect컴포넌트 바로 사용할 수 있을까?
쉽지 않다.

그렇다면 FrameworkSelect 컴포넌트에서 도메인을 제거한다면??

우선 props 네이밍에서 도메인 맥락을 제거한다
이렇게 도메인 맥락을 제거하면, 일반적인 이름으로 바뀌게 된다

이 부분이 굉장히 중요한데,
컴포넌트 인터페이스는 일반적일수록 이해하기 쉽다

도메인을 걷어내니 아까 설계했던 Select 컴포넌트의 attribue와 네이밍이 같아졌다.
Select 컴포넌트에 대한 지식을 기반으로 이 컴포넌트의 동작을 예측할 수 있다

Select Attribute와 비슷해졌다는 맥락에서 한가지 런닝을 얻을 수 있는데,

바로 컴포넌트 인터페이스가 표준에 가까울 수록 많은 사람들이 쉽게 이해할 수 있다는 것

FrameworkSelect컴포넌트는 도메인을 모르게 되면서 MultiSelect라는 일반적인 이름을 갖게 되었다.

도메인을 알고 있는 컴포넌트 FrameworkSelect컴포넌트와 도메인을 모르는 MultiSelect컴포넌트를 구분할 수 있다

FrameworkSelect 컴포넌트는 container가 아니지만 데이터에 접근, 외부 의존성이 없어 재사용하기 좋다
= 비지니스 로직은 스스로 처리하되, UI로직은 위임하는 방식이다


컴포넌트가 변경에 유연하기 위한 세가지 특징

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

1. 인터페이스를 먼저 고민하기


구현해야 하는 기능이 이미 만들어져 있다고 가정하고, 그것을 사용하듯이 작성하는 방법
위 이미지를 참고하면 FrameworkSelect컴포넌트를 작성할 때, 이미 MultiSelect가 있다는 가정하에 작성하는것이다

이미 만든 Select와 데이터 흐름이 동일하다
메뉴가 열려있는 Select와 같다고 생각해보자
좌측패널은 옵션을 제공하는 Menu,
우측패널은 현재 선택된 값을 보여주는데 그 값을 다른 컴포넌트로 표현된 부분이라고 볼 수 있다


UI에 속지 않고 데이터 흐름을 파악한다면 쉽게 구조를 잡을 수 있다

  1. 의도가 무엇인지
  2. 컴포넌트가 해야하는 기능이 무엇인지
  3. 어떤 데이터를 관리하고 있는지
  4. 그리고 이것이 인터페이스로 어떻게 표현되어야 하는지

위의 4가지 항목이 구현보다 중요하다


2. 컴포넌트를 나누는 이유

컴포넌트를 나누는 이유를 다시 한번 생각해보는 습관을 갖는것

우리는 모든 로직이 한곳에 있으면 파악하기 어렵다는 문제를 이미 알고있다. 그래서 하나의 애플리케이션을 여러개의 컴포넌트로 나누어 개발한다


3. 잘 만든 컴포넌트

이미 해결한 문제를 또 해결할 필요가 없기 때문에 컴포넌트를 잘 만들어야 한다
변경에 유연한 코드는 안정적으로 비지니스를 유지하고, 빠른 개발이 가능하다

0개의 댓글