효과적인 컴포넌트가 왜 필요할까?

개발 과정에는 SPRINT 단위가 존재한다. 팀이 일정량의 작업을 완료하는 시간을 정해두고 더 나은 소프트웨어를 제공하기 위해 발전, 업데이트 하는 단위라고 볼 수 있다.

이때, 특정 서비스의 품질을 높이기 위해 매 SPRINT마다 많은 변화가 일어난다.

프론트엔드 역할의 특성 상 다음과 같은 요소들을 목적으로 한다.

  1. 사용자 경험을 높이기 위한 최대의 노력과 중요하다고 생각하는 마인드 셋
  2. 유지보수가 가능하도록 코드를 작성하는 능력
  3. 서비스가 살아있도록 느끼게 하는 오너십

… 등등

📌 변화는 `니즈(Needs)`에 따라 발생하고 제품은 `고객`에 맞추어 성장한다

위와 같은 목적을 달성하기 위해서는 변화에 대해 두려워해서는 안되지만, 우리의 코드나 프로젝트에 들어간 시간노력이 없이지는 것은 누구에게나 힘든 일이다.

“변화는 예측이 불가능하다!”

그렇다면

우리는 변화에 어떻게 대응해야 하는가?

코드를 어떻게 작성해야 변화에 유연하게 대처할 수 있을까?

무엇이 효과적이고 유연한 컴포넌트일까?


적당히~’가 대체 뭘까요?

제품(Product)가 만들어지는 흐름은 다음과 같다.

작은 컴포넌트를 합치고 적당한 범위로 코드를 분리한다.

→ 이 과정에서 컴포넌트의 목적과 코드의 직관성을 잃는 경우가 존재한다.

📌 `적당히`를 정의할 수는 없을까요? (컴포넌트 분리 관점에서)

컴포넌트는 이래야 한다!

  1. Headless 기반의 추상화
  2. 한 가지 역할만 수행
  3. 도메인 포함 여부에 따른 분리

컴포넌트란?

  • Data : 외부에서 주입받은 데이터, State와 같은 내부 데이터를 관리
    const [state, setState] = useState(false);
  • UI : User Interface (데이터를 어떻게 보여줄 것인가?)
    //<button
    	onClick={() => {
    		setState(x => !x);
    	}}
    //>
  • Interaction : 사용자와 어떻게 상호작용할 것인가?
    <button>
    	버튼 {state ? 'on' : 'off'}
    </button>

1. 컴포넌트의 데이터와 UI를 분리하자!

“어떻게 보여줄 것인가?” 에 관한 부분은 (UI) 부분은 디자인에 의존한다.

그러면 컴포넌트에서 데이터와 UI를 분리해보는 것은 어떨까?

📌 디자인에 의존하는 UI를 컴포넌트가 관리하는 데이터와 분리해보자!

  • 데이터 :
    const { headers, body, view } = useCalendar()
    달력을 사용하는데 필요한 계산을 useCalendar에 위임함으로서, UI를 자유롭게 수정해도 문제가 생기지 않는다. (데이터를 분리하여 자유로운 형태를 만든 것) ⇒ 데이터에만 집중하여 모듈화할 수 있다.

    오로지 데이터에만 집중하여 모듈화 할 수 있는 패턴을 Headless라고 한다.

⇒ hooks로 모듈화 하는 방법!


2. 한 가지 일만 하는 컴포넌트의 조합으로 기능을 구성하자!

이전에 구성했던 드롭다운 관련 내용과 비슷한 내용이 나와서 반가웠다.

  • isOpen : 메뉴의 노출 상태 데이터를 관리하는 Dropdown
  • Trigger : 상태를 바꾸기 위한 상호작용을 관리하는 Dropdown.Trigger
  • Menu : 옵션 영역의 열고 닫힌 상태를 통해 노출여부가 결정되므로, Dropdown.Menu를 따로 구성
  • Item : Menu를 구성하는 각각의 아이템은 상호작용을 담당 → 분리한다.

다루고 있는 데이터와 역할을 기준으로 분리하였다.

function Select({ label, trigger, value, onChange, options }: Props) {
	return( 
		<Dropdown label={label} value={value} onChange={onChange}>
			<Dropdown.Trigger as={trigger}>
			<Dropdown.Menu>
				{options.map(option => (
					<Dropdown.Item>{option}</Dropdown.Item>
				))}
			</Dropdown.Menu>
		<Dropdown>
	);
}

위와 같이 구성하여 사용하는 경우, 각 컴포넌트들은 서로의 존재를 서로 알 수 없으므로, 변경이 유연해진다.

→ 다른 부분이 변경되더라도 Select 컴포넌트는 영향이 가지 않기 때문!

두 UI는 완전히 다른 것 같아 보이지만 다음과 같은 공통점을 뽑을 수 있다.

  • 옵션이 존재
  • 선택한 값을 보여주는 기능

Select 에서 힌트를 얻어 새로운 UI를 구성할 수 있다.

위와 같이 살펴보았을 때, Select에서 구분한 기능과 데이터 컴포넌트를 통해 재사용이 가능하며, 전체적인 흐름을 보았을 때, Select와 구성요소가 크게 다르지 않음을 알 수 있다.

📌 데이터와 UI를 분리하고 한 가지 기능만 할 수 있도록 컴포넌트를 생성하는 것은 컴포넌트 변경에 `유연하게 대처`하고 `재사용성`을 늘릴 수 있는 방법이 된다!

3. 도메인 분리하기 (레이어링)

“컴포넌트를 주입받은 것처럼 데이터도 주입받는 것은 어떨까?”

📌 언제 데이터를 주입받고, 언제 데이터를 스스로 가져올 것인가?

위 컴포넌트를 구성해야 한다고 할 때, 이전에 구성한 Select UI를 가져오기 힘든 이유가 무엇일까?

(현재 글에서는 빠졌지만 강의에서는 Framework라는 도메인을 넣었음!)

Framework라는 도메인을 분리하여 사용한다면 일반적인 이름으로 변경된다.

📌 컴포넌트는 일반적인 인터페이스로 표현해야 이해하기 쉽다.

interfaceProps 이름은 표준에 가까울수록 편리하다

ex) FrameworkSelectMultiSelect와 같은 형식으로 구성

  • 일반적인 이름을 가진 MultiSelect
  • 도메인을 모르는 경우와 포함하는 경우를 구분 → Framework 도메인에서는 container가 아님에도 데이터에 접근할 수 있다. 필요한 데이터를 직접 관리할 때, 자율적이고 외부 의존성이 없어 재사용이 편리하다.

좋은 컴포넌트를 구성하기 위해 할 수 있는 Action들

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

📌 구현해야하는 기능이 이미 있다고 가정하고 사용하듯이 작성해보기
  • 의도가 무엇인가?
  • 이 컴포넌트의 기능은 무엇인가?
  • 어떻게 표현되어야 하는가?

컴포넌트의 기능과 어떤 데이터를 관리하고 있는가? → 이것을 어떻게 표현할 것인가?

구현보다 중요한 문제!!!

2. 컴포넌트를 나누는 이유가 뭔지 고민해보기

📌 분리했을 때 복잡도를 낮추는가? 컴포넌트를 분리했을 때 재사용할 수 있는가?

더 좋은 코드를 구성하는 것은 복잡도를 낮추고, 재사용이 가능하며 변경에 유연하게 다시 구성할 수 있는 것


결론

변화를 대응하고 대처하기 위한 컴포넌트를 구성하는 관점

  1. 컴포넌트의 데이터와 UI를 분리하기
  2. 한 가지 일만 하는 컴포넌트의 조합으로 기능을 만들기
  3. 도메인을 분리하여 표준화한 인터페이스와 이름으로 구성하기

따라볼 수 있는 Actions

  1. 이미 존재한다고 가정하고 인터페이스 먼저 구성해보기
  2. 컴포넌트를 나누는 것이 더 좋은 일인지 고민해보기

“위에서 본 내용은 좋은 코드와 컴포넌트를 구성하기 위한 수많은 관점 중 하나다.”

우리가 가지고 있는 관점에 유의미한 관점을 추가하여 변경에 유연하게 대처할 수 있는 방향성에 대해 고민해보는 것이 중요하다.


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

profile
상상을 현실로 만드는 FE

0개의 댓글

Powered by GraphCDN, the GraphQL CDN